diff --git a/src/tools/miri/.github/workflows/ci.yml b/src/tools/miri/.github/workflows/ci.yml index 209fd622202e2..81df0964d5912 100644 --- a/src/tools/miri/.github/workflows/ci.yml +++ b/src/tools/miri/.github/workflows/ci.yml @@ -126,7 +126,7 @@ jobs: with: fetch-depth: 256 # get a bit more of the history - name: install josh-proxy - run: RUSTFLAGS="--cap-lints warn" cargo +stable install josh-proxy --git https://github.com/josh-project/josh --tag r23.12.04 + run: cargo +stable install josh-proxy --git https://github.com/josh-project/josh --tag r24.10.04 - name: setup bot git name and email run: | git config --global user.name 'The Miri Cronjob Bot' diff --git a/src/tools/miri/CONTRIBUTING.md b/src/tools/miri/CONTRIBUTING.md index dd283b7c0a857..5a08ac9af60a5 100644 --- a/src/tools/miri/CONTRIBUTING.md +++ b/src/tools/miri/CONTRIBUTING.md @@ -290,7 +290,7 @@ We use the [`josh` proxy](https://github.com/josh-project/josh) to transmit chan rustc and Miri repositories. You can install it as follows: ```sh -RUSTFLAGS="--cap-lints=warn" cargo +stable install josh-proxy --git https://github.com/josh-project/josh --tag r23.12.04 +cargo +stable install josh-proxy --git https://github.com/josh-project/josh --tag r24.10.04 ``` Josh will automatically be started and stopped by `./miri`. diff --git a/src/tools/miri/README.md b/src/tools/miri/README.md index 9042c5da57716..4ae901be9b43b 100644 --- a/src/tools/miri/README.md +++ b/src/tools/miri/README.md @@ -217,8 +217,8 @@ degree documented below): - For every other target with OS `linux`, `macos`, or `windows`, Miri should generally work, but we make no promises and we don't run tests for such targets. - We have unofficial support (not maintained by the Miri team itself) for some further operating systems. - - `solaris` / `illumos`: maintained by @devnexen. Supports `std::{env, thread, sync}`, but not `std::fs`. - - `freebsd`: **maintainer wanted**. Supports `std::env` and parts of `std::{thread, fs}`, but not `std::sync`. + - `solaris` / `illumos`: maintained by @devnexen. Supports the entire test suite. + - `freebsd`: maintained by @YohDeadfall. Supports `std::env` and parts of `std::{thread, fs}`, but not `std::sync`. - `android`: **maintainer wanted**. Support very incomplete, but a basic "hello world" works. - `wasi`: **maintainer wanted**. Support very incomplete, not even standard output works, but an empty `main` function works. - For targets on other operating systems, Miri might fail before even reaching the `main` function. diff --git a/src/tools/miri/ci/ci.sh b/src/tools/miri/ci/ci.sh index 0751f86da8529..5583030b490ae 100755 --- a/src/tools/miri/ci/ci.sh +++ b/src/tools/miri/ci/ci.sh @@ -147,13 +147,14 @@ case $HOST_TARGET in # Extra tier 2 TEST_TARGET=arm-unknown-linux-gnueabi run_tests TEST_TARGET=s390x-unknown-linux-gnu run_tests # big-endian architecture of choice + # Not officially supported tier 2 + TEST_TARGET=x86_64-unknown-illumos run_tests + TEST_TARGET=x86_64-pc-solaris run_tests # Partially supported targets (tier 2) BASIC="empty_main integer heap_alloc libc-mem vec string btreemap" # ensures we have the basics: pre-main code, system allocator UNIX="hello panic/panic panic/unwind concurrency/simple atomic libc-mem libc-misc libc-random env num_cpus" # the things that are very similar across all Unixes, and hence easily supported there TEST_TARGET=x86_64-unknown-freebsd run_tests_minimal $BASIC $UNIX time hashmap random threadname pthread fs libc-pipe TEST_TARGET=i686-unknown-freebsd run_tests_minimal $BASIC $UNIX time hashmap random threadname pthread fs libc-pipe - TEST_TARGET=x86_64-unknown-illumos run_tests_minimal $BASIC $UNIX time hashmap random thread sync available-parallelism tls libc-pipe fs - TEST_TARGET=x86_64-pc-solaris run_tests_minimal $BASIC $UNIX time hashmap random thread sync available-parallelism tls libc-pipe fs TEST_TARGET=aarch64-linux-android run_tests_minimal $BASIC $UNIX time hashmap random sync threadname pthread epoll eventfd TEST_TARGET=wasm32-wasip2 run_tests_minimal $BASIC wasm TEST_TARGET=wasm32-unknown-unknown run_tests_minimal no_std empty_main wasm # this target doesn't really have std diff --git a/src/tools/miri/etc/rust_analyzer_vscode.json b/src/tools/miri/etc/rust_analyzer_vscode.json index 5e51c3e88802c..c646953e92b7c 100644 --- a/src/tools/miri/etc/rust_analyzer_vscode.json +++ b/src/tools/miri/etc/rust_analyzer_vscode.json @@ -5,21 +5,19 @@ "cargo-miri/Cargo.toml", "miri-script/Cargo.toml", ], - "rust-analyzer.check.invocationLocation": "root", "rust-analyzer.check.invocationStrategy": "once", "rust-analyzer.check.overrideCommand": [ - "env", - "MIRI_AUTO_OPS=no", "./miri", "clippy", // make this `check` when working with a locally built rustc "--message-format=json", ], + "rust-analyzer.cargo.extraEnv": { + "MIRI_AUTO_OPS": "no", + "MIRI_IN_RA": "1", + }, // Contrary to what the name suggests, this also affects proc macros. - "rust-analyzer.cargo.buildScripts.invocationLocation": "root", "rust-analyzer.cargo.buildScripts.invocationStrategy": "once", "rust-analyzer.cargo.buildScripts.overrideCommand": [ - "env", - "MIRI_AUTO_OPS=no", "./miri", "check", "--message-format=json", diff --git a/src/tools/miri/miri b/src/tools/miri/miri index ac1a7211c4ef8..b1b146d79905b 100755 --- a/src/tools/miri/miri +++ b/src/tools/miri/miri @@ -3,12 +3,16 @@ set -e # We want to call the binary directly, so we need to know where it ends up. ROOT_DIR="$(dirname "$0")" MIRI_SCRIPT_TARGET_DIR="$ROOT_DIR"/miri-script/target -# If stdout is not a terminal and we are not on CI, assume that we are being invoked by RA, and use JSON output. -if ! [ -t 1 ] && [ -z "$CI" ]; then +TOOLCHAIN="+nightly" +# If we are being invoked for RA, use JSON output and the default toolchain (to make proc-macros +# work in RA). This needs a different target dir to avoid mixing up the builds. +if [ -n "$MIRI_IN_RA" ]; then MESSAGE_FORMAT="--message-format=json" + TOOLCHAIN="" + MIRI_SCRIPT_TARGET_DIR="$MIRI_SCRIPT_TARGET_DIR"/ra fi # We need a nightly toolchain, for `-Zroot-dir`. -cargo +nightly build $CARGO_EXTRA_FLAGS --manifest-path "$ROOT_DIR"/miri-script/Cargo.toml \ +cargo $TOOLCHAIN build $CARGO_EXTRA_FLAGS --manifest-path "$ROOT_DIR"/miri-script/Cargo.toml \ -Zroot-dir="$ROOT_DIR" \ -q --target-dir "$MIRI_SCRIPT_TARGET_DIR" $MESSAGE_FORMAT || \ ( echo "Failed to build miri-script. Is the 'nightly' toolchain installed?"; exit 1 ) diff --git a/src/tools/miri/miri-script/src/commands.rs b/src/tools/miri/miri-script/src/commands.rs index 75ac999e8be75..17a7c06b52535 100644 --- a/src/tools/miri/miri-script/src/commands.rs +++ b/src/tools/miri/miri-script/src/commands.rs @@ -423,7 +423,7 @@ impl Command { .map(|path| path.into_os_string().into_string().unwrap()) .collect() } else { - benches.into_iter().map(Into::into).collect() + benches.into_iter().collect() }; let target_flag = if let Some(target) = target { let mut flag = OsString::from("--target="); @@ -564,6 +564,10 @@ impl Command { if bless { e.sh.set_var("RUSTC_BLESS", "Gesundheit"); } + if e.sh.var("MIRI_TEST_TARGET").is_ok() { + // Avoid trouble due to an incorrectly set env var. + bail!("MIRI_TEST_TARGET must not be set when invoking `./miri test`"); + } if let Some(target) = target { // Tell the harness which target to test. e.sh.set_var("MIRI_TEST_TARGET", target); diff --git a/src/tools/miri/miri-script/src/main.rs b/src/tools/miri/miri-script/src/main.rs index a80fed8fcb65e..279bdf8cc3f48 100644 --- a/src/tools/miri/miri-script/src/main.rs +++ b/src/tools/miri/miri-script/src/main.rs @@ -111,6 +111,7 @@ pub enum Command { /// `rustup-toolchain-install-master` must be installed for this to work. Toolchain { /// Flags that are passed through to `rustup-toolchain-install-master`. + #[arg(trailing_var_arg = true, allow_hyphen_values = true)] flags: Vec, }, /// Pull and merge Miri changes from the rustc repo. diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index 24bef6026d4ce..fa5dbb99e8170 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -13170cd787cb733ed24842ee825bcbd98dc01476 +01706e1a34c87656fcbfce198608f4cd2ac6461a diff --git a/src/tools/miri/src/concurrency/cpu_affinity.rs b/src/tools/miri/src/concurrency/cpu_affinity.rs index 4e6bca93c5a01..b47b614cf5f0e 100644 --- a/src/tools/miri/src/concurrency/cpu_affinity.rs +++ b/src/tools/miri/src/concurrency/cpu_affinity.rs @@ -54,8 +54,8 @@ impl CpuAffinityMask { let chunk = self.0[start..].first_chunk_mut::<4>().unwrap(); let offset = cpu % 32; *chunk = match target.options.endian { - Endian::Little => (u32::from_le_bytes(*chunk) | 1 << offset).to_le_bytes(), - Endian::Big => (u32::from_be_bytes(*chunk) | 1 << offset).to_be_bytes(), + Endian::Little => (u32::from_le_bytes(*chunk) | (1 << offset)).to_le_bytes(), + Endian::Big => (u32::from_be_bytes(*chunk) | (1 << offset)).to_be_bytes(), }; } 8 => { @@ -63,8 +63,8 @@ impl CpuAffinityMask { let chunk = self.0[start..].first_chunk_mut::<8>().unwrap(); let offset = cpu % 64; *chunk = match target.options.endian { - Endian::Little => (u64::from_le_bytes(*chunk) | 1 << offset).to_le_bytes(), - Endian::Big => (u64::from_be_bytes(*chunk) | 1 << offset).to_be_bytes(), + Endian::Little => (u64::from_le_bytes(*chunk) | (1 << offset)).to_le_bytes(), + Endian::Big => (u64::from_be_bytes(*chunk) | (1 << offset)).to_be_bytes(), }; } other => bug!("chunk size not supported: {other}"), diff --git a/src/tools/miri/src/concurrency/sync.rs b/src/tools/miri/src/concurrency/sync.rs index ef4034cc0c11e..14c72e9398add 100644 --- a/src/tools/miri/src/concurrency/sync.rs +++ b/src/tools/miri/src/concurrency/sync.rs @@ -422,7 +422,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { mutex_ref: MutexRef, retval_dest: Option<(Scalar, MPlaceTy<'tcx>)>, } - @unblock = |this| { + |this, unblock: UnblockKind| { + assert_eq!(unblock, UnblockKind::Ready); + assert!(!this.mutex_is_locked(&mutex_ref)); this.mutex_lock(&mutex_ref); @@ -538,7 +540,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { retval: Scalar, dest: MPlaceTy<'tcx>, } - @unblock = |this| { + |this, unblock: UnblockKind| { + assert_eq!(unblock, UnblockKind::Ready); this.rwlock_reader_lock(id); this.write_scalar(retval, &dest)?; interp_ok(()) @@ -623,7 +626,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { retval: Scalar, dest: MPlaceTy<'tcx>, } - @unblock = |this| { + |this, unblock: UnblockKind| { + assert_eq!(unblock, UnblockKind::Ready); this.rwlock_writer_lock(id); this.write_scalar(retval, &dest)?; interp_ok(()) @@ -677,25 +681,29 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { retval_timeout: Scalar, dest: MPlaceTy<'tcx>, } - @unblock = |this| { - // The condvar was signaled. Make sure we get the clock for that. - if let Some(data_race) = &this.machine.data_race { - data_race.acquire_clock( - &this.machine.sync.condvars[condvar].clock, - &this.machine.threads, - ); + |this, unblock: UnblockKind| { + match unblock { + UnblockKind::Ready => { + // The condvar was signaled. Make sure we get the clock for that. + if let Some(data_race) = &this.machine.data_race { + data_race.acquire_clock( + &this.machine.sync.condvars[condvar].clock, + &this.machine.threads, + ); + } + // Try to acquire the mutex. + // The timeout only applies to the first wait (until the signal), not for mutex acquisition. + this.condvar_reacquire_mutex(&mutex_ref, retval_succ, dest) + } + UnblockKind::TimedOut => { + // We have to remove the waiter from the queue again. + let thread = this.active_thread(); + let waiters = &mut this.machine.sync.condvars[condvar].waiters; + waiters.retain(|waiter| *waiter != thread); + // Now get back the lock. + this.condvar_reacquire_mutex(&mutex_ref, retval_timeout, dest) + } } - // Try to acquire the mutex. - // The timeout only applies to the first wait (until the signal), not for mutex acquisition. - this.condvar_reacquire_mutex(&mutex_ref, retval_succ, dest) - } - @timeout = |this| { - // We have to remove the waiter from the queue again. - let thread = this.active_thread(); - let waiters = &mut this.machine.sync.condvars[condvar].waiters; - waiters.retain(|waiter| *waiter != thread); - // Now get back the lock. - this.condvar_reacquire_mutex(&mutex_ref, retval_timeout, dest) } ), ); @@ -752,25 +760,29 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { dest: MPlaceTy<'tcx>, errno_timeout: IoError, } - @unblock = |this| { - let futex = futex_ref.0.borrow(); - // Acquire the clock of the futex. - if let Some(data_race) = &this.machine.data_race { - data_race.acquire_clock(&futex.clock, &this.machine.threads); + |this, unblock: UnblockKind| { + match unblock { + UnblockKind::Ready => { + let futex = futex_ref.0.borrow(); + // Acquire the clock of the futex. + if let Some(data_race) = &this.machine.data_race { + data_race.acquire_clock(&futex.clock, &this.machine.threads); + } + // Write the return value. + this.write_scalar(retval_succ, &dest)?; + interp_ok(()) + }, + UnblockKind::TimedOut => { + // Remove the waiter from the futex. + let thread = this.active_thread(); + let mut futex = futex_ref.0.borrow_mut(); + futex.waiters.retain(|waiter| waiter.thread != thread); + // Set errno and write return value. + this.set_last_error(errno_timeout)?; + this.write_scalar(retval_timeout, &dest)?; + interp_ok(()) + }, } - // Write the return value. - this.write_scalar(retval_succ, &dest)?; - interp_ok(()) - } - @timeout = |this| { - // Remove the waiter from the futex. - let thread = this.active_thread(); - let mut futex = futex_ref.0.borrow_mut(); - futex.waiters.retain(|waiter| waiter.thread != thread); - // Set errno and write return value. - this.set_last_error(errno_timeout)?; - this.write_scalar(retval_timeout, &dest)?; - interp_ok(()) } ), ); diff --git a/src/tools/miri/src/concurrency/thread.rs b/src/tools/miri/src/concurrency/thread.rs index 730c27d0160bb..6d22dd8d68d93 100644 --- a/src/tools/miri/src/concurrency/thread.rs +++ b/src/tools/miri/src/concurrency/thread.rs @@ -19,7 +19,7 @@ use crate::concurrency::data_race; use crate::shims::tls; use crate::*; -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq)] enum SchedulingAction { /// Execute step on the active thread. ExecuteStep, @@ -30,6 +30,7 @@ enum SchedulingAction { } /// What to do with TLS allocations from terminated threads +#[derive(Clone, Copy, Debug, PartialEq)] pub enum TlsAllocAction { /// Deallocate backing memory of thread-local statics as usual Deallocate, @@ -38,71 +39,18 @@ pub enum TlsAllocAction { Leak, } -/// Trait for callbacks that are executed when a thread gets unblocked. -pub trait UnblockCallback<'tcx>: VisitProvenance { - /// Will be invoked when the thread was unblocked the "regular" way, - /// i.e. whatever event it was blocking on has happened. - fn unblock(self: Box, ecx: &mut InterpCx<'tcx, MiriMachine<'tcx>>) -> InterpResult<'tcx>; - - /// Will be invoked when the timeout ellapsed without the event the - /// thread was blocking on having occurred. - fn timeout(self: Box, _ecx: &mut InterpCx<'tcx, MiriMachine<'tcx>>) - -> InterpResult<'tcx>; +/// The argument type for the "unblock" callback, indicating why the thread got unblocked. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum UnblockKind { + /// Operation completed successfully, thread continues normal execution. + Ready, + /// The operation did not complete within its specified duration. + TimedOut, } -pub type DynUnblockCallback<'tcx> = Box + 'tcx>; - -#[macro_export] -macro_rules! callback { - ( - @capture<$tcx:lifetime $(,)? $($lft:lifetime),*> { $($name:ident: $type:ty),* $(,)? } - @unblock = |$this:ident| $unblock:block - ) => { - callback!( - @capture<$tcx, $($lft),*> { $($name: $type),* } - @unblock = |$this| $unblock - @timeout = |_this| { - unreachable!( - "timeout on a thread that was blocked without a timeout (or someone forgot to overwrite this method)" - ) - } - ) - }; - ( - @capture<$tcx:lifetime $(,)? $($lft:lifetime),*> { $($name:ident: $type:ty),* $(,)? } - @unblock = |$this:ident| $unblock:block - @timeout = |$this_timeout:ident| $timeout:block - ) => {{ - struct Callback<$tcx, $($lft),*> { - $($name: $type,)* - _phantom: std::marker::PhantomData<&$tcx ()>, - } - - impl<$tcx, $($lft),*> VisitProvenance for Callback<$tcx, $($lft),*> { - #[allow(unused_variables)] - fn visit_provenance(&self, visit: &mut VisitWith<'_>) { - $( - self.$name.visit_provenance(visit); - )* - } - } - - impl<$tcx, $($lft),*> UnblockCallback<$tcx> for Callback<$tcx, $($lft),*> { - fn unblock(self: Box, $this: &mut MiriInterpCx<$tcx>) -> InterpResult<$tcx> { - #[allow(unused_variables)] - let Callback { $($name,)* _phantom } = *self; - $unblock - } - fn timeout(self: Box, $this_timeout: &mut MiriInterpCx<$tcx>) -> InterpResult<$tcx> { - #[allow(unused_variables)] - let Callback { $($name,)* _phantom } = *self; - $timeout - } - } - - Box::new(Callback { $($name,)* _phantom: std::marker::PhantomData }) - }} -} +/// Type alias for unblock callbacks, i.e. machine callbacks invoked when +/// a thread gets unblocked. +pub type DynUnblockCallback<'tcx> = DynMachineCallback<'tcx, UnblockKind>; /// A thread identifier. #[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)] @@ -656,7 +604,8 @@ impl<'tcx> ThreadManager<'tcx> { @capture<'tcx> { joined_thread_id: ThreadId, } - @unblock = |this| { + |this, unblock: UnblockKind| { + assert_eq!(unblock, UnblockKind::Ready); if let Some(data_race) = &mut this.machine.data_race { data_race.thread_joined(&this.machine.threads, joined_thread_id); } @@ -842,7 +791,7 @@ trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> { // 2. Make the scheduler the only place that can change the active // thread. let old_thread = this.machine.threads.set_active_thread_id(thread); - callback.timeout(this)?; + callback.call(this, UnblockKind::TimedOut)?; this.machine.threads.set_active_thread_id(old_thread); } // found_callback can remain None if the computer's clock @@ -1084,7 +1033,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { }; // The callback must be executed in the previously blocked thread. let old_thread = this.machine.threads.set_active_thread_id(thread); - callback.unblock(this)?; + callback.call(this, UnblockKind::Ready)?; this.machine.threads.set_active_thread_id(old_thread); interp_ok(()) } diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs index adfec33beac60..ca8dbdac125d0 100644 --- a/src/tools/miri/src/helpers.rs +++ b/src/tools/miri/src/helpers.rs @@ -262,6 +262,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { }) } + /// Helper function to get a `libc` constant as an `u64`. + fn eval_libc_u64(&self, name: &str) -> u64 { + // TODO: Cache the result. + self.eval_libc(name).to_u64().unwrap_or_else(|_err| { + panic!("required libc item has unexpected type (not `u64`): {name}") + }) + } + /// Helper function to get a `windows` constant as a `Scalar`. fn eval_windows(&self, module: &str, name: &str) -> Scalar { self.eval_context_ref().eval_path_scalar(&["std", "sys", "pal", "windows", module, name]) diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs index e02d51afceff0..a53b22c804158 100644 --- a/src/tools/miri/src/lib.rs +++ b/src/tools/miri/src/lib.rs @@ -13,6 +13,8 @@ #![feature(strict_overflow_ops)] #![feature(pointer_is_aligned_to)] #![feature(unqualified_local_imports)] +#![feature(derive_coerce_pointee)] +#![feature(arbitrary_self_types)] // Configure clippy and other lints #![allow( clippy::collapsible_else_if, @@ -126,8 +128,8 @@ pub use crate::concurrency::sync::{ CondvarId, EvalContextExt as _, MutexRef, RwLockId, SynchronizationObjects, }; pub use crate::concurrency::thread::{ - BlockReason, EvalContextExt as _, StackEmptyCallback, ThreadId, ThreadManager, TimeoutAnchor, - TimeoutClock, UnblockCallback, + BlockReason, DynUnblockCallback, EvalContextExt as _, StackEmptyCallback, ThreadId, + ThreadManager, TimeoutAnchor, TimeoutClock, UnblockKind, }; pub use crate::diagnostics::{ EvalContextExt as _, NonHaltingDiagnostic, TerminationInfo, report_error, @@ -139,8 +141,8 @@ pub use crate::eval::{ pub use crate::helpers::{AccessKind, EvalContextExt as _}; pub use crate::intrinsics::EvalContextExt as _; pub use crate::machine::{ - AllocExtra, FrameExtra, MemoryKind, MiriInterpCx, MiriInterpCxExt, MiriMachine, MiriMemoryKind, - PrimitiveLayouts, Provenance, ProvenanceExtra, + AllocExtra, DynMachineCallback, FrameExtra, MachineCallback, MemoryKind, MiriInterpCx, + MiriInterpCxExt, MiriMachine, MiriMemoryKind, PrimitiveLayouts, Provenance, ProvenanceExtra, }; pub use crate::mono_hash_map::MonoHashMap; pub use crate::operator::EvalContextExt as _; diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index 5e8f616a37ea0..845ba484326f0 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -1723,3 +1723,69 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> { Cow::Borrowed(ecx.machine.union_data_ranges.entry(ty).or_insert_with(compute_range)) } } + +/// Trait for callbacks handling asynchronous machine operations. +pub trait MachineCallback<'tcx, T>: VisitProvenance { + /// The function to be invoked when the callback is fired. + fn call( + self: Box, + ecx: &mut InterpCx<'tcx, MiriMachine<'tcx>>, + arg: T, + ) -> InterpResult<'tcx>; +} + +/// Type alias for boxed machine callbacks with generic argument type. +pub type DynMachineCallback<'tcx, T> = Box + 'tcx>; + +/// Creates a `DynMachineCallback`: +/// +/// ```rust +/// callback!( +/// @capture<'tcx> { +/// var1: Ty1, +/// var2: Ty2<'tcx>, +/// } +/// |this, arg: ArgTy| { +/// // Implement the callback here. +/// todo!() +/// } +/// ) +/// ``` +/// +/// All the argument types must implement `VisitProvenance`. +#[macro_export] +macro_rules! callback { + (@capture<$tcx:lifetime $(,)? $($lft:lifetime),*> + { $($name:ident: $type:ty),* $(,)? } + |$this:ident, $arg:ident: $arg_ty:ty| $body:expr $(,)?) => {{ + struct Callback<$tcx, $($lft),*> { + $($name: $type,)* + _phantom: std::marker::PhantomData<&$tcx ()>, + } + + impl<$tcx, $($lft),*> VisitProvenance for Callback<$tcx, $($lft),*> { + fn visit_provenance(&self, _visit: &mut VisitWith<'_>) { + $( + self.$name.visit_provenance(_visit); + )* + } + } + + impl<$tcx, $($lft),*> MachineCallback<$tcx, $arg_ty> for Callback<$tcx, $($lft),*> { + fn call( + self: Box, + $this: &mut MiriInterpCx<$tcx>, + $arg: $arg_ty + ) -> InterpResult<$tcx> { + #[allow(unused_variables)] + let Callback { $($name,)* _phantom } = *self; + $body + } + } + + Box::new(Callback { + $($name,)* + _phantom: std::marker::PhantomData + }) + }}; +} diff --git a/src/tools/miri/src/shims/files.rs b/src/tools/miri/src/shims/files.rs index f673b834be249..73425eee51569 100644 --- a/src/tools/miri/src/shims/files.rs +++ b/src/tools/miri/src/shims/files.rs @@ -1,6 +1,7 @@ use std::any::Any; use std::collections::BTreeMap; use std::io::{IsTerminal, Read, SeekFrom, Write}; +use std::marker::CoercePointee; use std::ops::Deref; use std::rc::{Rc, Weak}; use std::{fs, io}; @@ -10,16 +11,132 @@ use rustc_abi::Size; use crate::shims::unix::UnixFileDescription; use crate::*; +/// A unique id for file descriptions. While we could use the address, considering that +/// is definitely unique, the address would expose interpreter internal state when used +/// for sorting things. So instead we generate a unique id per file description is the name +/// for all `dup`licates and is never reused. +#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Ord, PartialOrd)] +pub struct FdId(usize); + +#[derive(Debug, Clone)] +struct FdIdWith { + id: FdId, + inner: T, +} + +/// A refcounted pointer to a file description, also tracking the +/// globally unique ID of this file description. +#[repr(transparent)] +#[derive(CoercePointee, Debug)] +pub struct FileDescriptionRef(Rc>); + +impl Clone for FileDescriptionRef { + fn clone(&self) -> Self { + FileDescriptionRef(self.0.clone()) + } +} + +impl Deref for FileDescriptionRef { + type Target = T; + fn deref(&self) -> &T { + &self.0.inner + } +} + +impl FileDescriptionRef { + pub fn id(&self) -> FdId { + self.0.id + } +} + +/// Holds a weak reference to the actual file description. +#[derive(Debug)] +pub struct WeakFileDescriptionRef(Weak>); + +impl Clone for WeakFileDescriptionRef { + fn clone(&self) -> Self { + WeakFileDescriptionRef(self.0.clone()) + } +} + +impl FileDescriptionRef { + pub fn downgrade(this: &Self) -> WeakFileDescriptionRef { + WeakFileDescriptionRef(Rc::downgrade(&this.0)) + } +} + +impl WeakFileDescriptionRef { + pub fn upgrade(&self) -> Option> { + self.0.upgrade().map(FileDescriptionRef) + } +} + +impl VisitProvenance for WeakFileDescriptionRef { + fn visit_provenance(&self, _visit: &mut VisitWith<'_>) { + // A weak reference can never be the only reference to some pointer or place. + // Since the actual file description is tracked by strong ref somewhere, + // it is ok to make this a NOP operation. + } +} + +/// A helper trait to indirectly allow downcasting on `Rc>`. +/// Ideally we'd just add a `FdIdWith: Any` bound to the `FileDescription` trait, +/// but that does not allow upcasting. +pub trait FileDescriptionExt: 'static { + fn into_rc_any(self: FileDescriptionRef) -> Rc; + + /// We wrap the regular `close` function generically, so both handle `Rc::into_inner` + /// and epoll interest management. + fn close_ref<'tcx>( + self: FileDescriptionRef, + communicate_allowed: bool, + ecx: &mut MiriInterpCx<'tcx>, + ) -> InterpResult<'tcx, io::Result<()>>; +} + +impl FileDescriptionExt for T { + fn into_rc_any(self: FileDescriptionRef) -> Rc { + self.0 + } + + fn close_ref<'tcx>( + self: FileDescriptionRef, + communicate_allowed: bool, + ecx: &mut MiriInterpCx<'tcx>, + ) -> InterpResult<'tcx, io::Result<()>> { + match Rc::into_inner(self.0) { + Some(fd) => { + // Remove entry from the global epoll_event_interest table. + ecx.machine.epoll_interests.remove(fd.id); + + fd.inner.close(communicate_allowed, ecx) + } + None => { + // Not the last reference. + interp_ok(Ok(())) + } + } + } +} + +pub type DynFileDescriptionRef = FileDescriptionRef; + +impl FileDescriptionRef { + pub fn downcast(self) -> Option> { + let inner = self.into_rc_any().downcast::>().ok()?; + Some(FileDescriptionRef(inner)) + } +} + /// Represents an open file description. -pub trait FileDescription: std::fmt::Debug + Any { +pub trait FileDescription: std::fmt::Debug + FileDescriptionExt { fn name(&self) -> &'static str; /// Reads as much as possible into the given buffer `ptr`. /// `len` indicates how many bytes we should try to read. /// `dest` is where the return value should be stored: number of bytes read, or `-1` in case of error. fn read<'tcx>( - &self, - _self_ref: &FileDescriptionRef, + self: FileDescriptionRef, _communicate_allowed: bool, _ptr: Pointer, _len: usize, @@ -33,8 +150,7 @@ pub trait FileDescription: std::fmt::Debug + Any { /// `len` indicates how many bytes we should try to write. /// `dest` is where the return value should be stored: number of bytes written, or `-1` in case of error. fn write<'tcx>( - &self, - _self_ref: &FileDescriptionRef, + self: FileDescriptionRef, _communicate_allowed: bool, _ptr: Pointer, _len: usize, @@ -54,11 +170,15 @@ pub trait FileDescription: std::fmt::Debug + Any { throw_unsup_format!("cannot seek on {}", self.name()); } + /// Close the file descriptor. fn close<'tcx>( - self: Box, + self, _communicate_allowed: bool, _ecx: &mut MiriInterpCx<'tcx>, - ) -> InterpResult<'tcx, io::Result<()>> { + ) -> InterpResult<'tcx, io::Result<()>> + where + Self: Sized, + { throw_unsup_format!("cannot close {}", self.name()); } @@ -77,21 +197,13 @@ pub trait FileDescription: std::fmt::Debug + Any { } } -impl dyn FileDescription { - #[inline(always)] - pub fn downcast(&self) -> Option<&T> { - (self as &dyn Any).downcast_ref() - } -} - impl FileDescription for io::Stdin { fn name(&self) -> &'static str { "stdin" } fn read<'tcx>( - &self, - _self_ref: &FileDescriptionRef, + self: FileDescriptionRef, communicate_allowed: bool, ptr: Pointer, len: usize, @@ -103,7 +215,7 @@ impl FileDescription for io::Stdin { // We want isolation mode to be deterministic, so we have to disallow all reads, even stdin. helpers::isolation_abort_error("`read` from stdin")?; } - let result = Read::read(&mut { self }, &mut bytes); + let result = Read::read(&mut &*self, &mut bytes); match result { Ok(read_size) => ecx.return_read_success(ptr, &bytes, read_size, dest), Err(e) => ecx.set_last_error_and_return(e, dest), @@ -121,8 +233,7 @@ impl FileDescription for io::Stdout { } fn write<'tcx>( - &self, - _self_ref: &FileDescriptionRef, + self: FileDescriptionRef, _communicate_allowed: bool, ptr: Pointer, len: usize, @@ -131,7 +242,7 @@ impl FileDescription for io::Stdout { ) -> InterpResult<'tcx> { let bytes = ecx.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?; // We allow writing to stderr even with isolation enabled. - let result = Write::write(&mut { self }, bytes); + let result = Write::write(&mut &*self, bytes); // Stdout is buffered, flush to make sure it appears on the // screen. This is the write() syscall of the interpreted // program, we want it to correspond to a write() syscall on @@ -155,8 +266,7 @@ impl FileDescription for io::Stderr { } fn write<'tcx>( - &self, - _self_ref: &FileDescriptionRef, + self: FileDescriptionRef, _communicate_allowed: bool, ptr: Pointer, len: usize, @@ -166,7 +276,7 @@ impl FileDescription for io::Stderr { let bytes = ecx.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?; // We allow writing to stderr even with isolation enabled. // No need to flush, stderr is not buffered. - let result = Write::write(&mut { self }, bytes); + let result = Write::write(&mut &*self, bytes); match result { Ok(write_size) => ecx.return_write_success(write_size, dest), Err(e) => ecx.set_last_error_and_return(e, dest), @@ -188,8 +298,7 @@ impl FileDescription for NullOutput { } fn write<'tcx>( - &self, - _self_ref: &FileDescriptionRef, + self: FileDescriptionRef, _communicate_allowed: bool, _ptr: Pointer, len: usize, @@ -201,91 +310,10 @@ impl FileDescription for NullOutput { } } -/// Structure contains both the file description and its unique identifier. -#[derive(Clone, Debug)] -pub struct FileDescWithId { - id: FdId, - file_description: Box, -} - -#[derive(Clone, Debug)] -pub struct FileDescriptionRef(Rc>); - -impl Deref for FileDescriptionRef { - type Target = dyn FileDescription; - - fn deref(&self) -> &Self::Target { - &*self.0.file_description - } -} - -impl FileDescriptionRef { - fn new(fd: impl FileDescription, id: FdId) -> Self { - FileDescriptionRef(Rc::new(FileDescWithId { id, file_description: Box::new(fd) })) - } - - pub fn close<'tcx>( - self, - communicate_allowed: bool, - ecx: &mut MiriInterpCx<'tcx>, - ) -> InterpResult<'tcx, io::Result<()>> { - // Destroy this `Rc` using `into_inner` so we can call `close` instead of - // implicitly running the destructor of the file description. - let id = self.get_id(); - match Rc::into_inner(self.0) { - Some(fd) => { - // Remove entry from the global epoll_event_interest table. - ecx.machine.epoll_interests.remove(id); - - fd.file_description.close(communicate_allowed, ecx) - } - None => interp_ok(Ok(())), - } - } - - pub fn downgrade(&self) -> WeakFileDescriptionRef { - WeakFileDescriptionRef { weak_ref: Rc::downgrade(&self.0) } - } - - pub fn get_id(&self) -> FdId { - self.0.id - } -} - -/// Holds a weak reference to the actual file description. -#[derive(Clone, Debug, Default)] -pub struct WeakFileDescriptionRef { - weak_ref: Weak>, -} - -impl WeakFileDescriptionRef { - pub fn upgrade(&self) -> Option { - if let Some(file_desc_with_id) = self.weak_ref.upgrade() { - return Some(FileDescriptionRef(file_desc_with_id)); - } - None - } -} - -impl VisitProvenance for WeakFileDescriptionRef { - fn visit_provenance(&self, _visit: &mut VisitWith<'_>) { - // A weak reference can never be the only reference to some pointer or place. - // Since the actual file description is tracked by strong ref somewhere, - // it is ok to make this a NOP operation. - } -} - -/// A unique id for file descriptions. While we could use the address, considering that -/// is definitely unique, the address would expose interpreter internal state when used -/// for sorting things. So instead we generate a unique id per file description is the name -/// for all `dup`licates and is never reused. -#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Ord, PartialOrd)] -pub struct FdId(usize); - /// The file descriptor table #[derive(Debug)] pub struct FdTable { - pub fds: BTreeMap, + pub fds: BTreeMap, /// Unique identifier for file description, used to differentiate between various file description. next_file_description_id: FdId, } @@ -313,8 +341,9 @@ impl FdTable { fds } - pub fn new_ref(&mut self, fd: impl FileDescription) -> FileDescriptionRef { - let file_handle = FileDescriptionRef::new(fd, self.next_file_description_id); + pub fn new_ref(&mut self, fd: T) -> FileDescriptionRef { + let file_handle = + FileDescriptionRef(Rc::new(FdIdWith { id: self.next_file_description_id, inner: fd })); self.next_file_description_id = FdId(self.next_file_description_id.0.strict_add(1)); file_handle } @@ -325,12 +354,16 @@ impl FdTable { self.insert(fd_ref) } - pub fn insert(&mut self, fd_ref: FileDescriptionRef) -> i32 { + pub fn insert(&mut self, fd_ref: DynFileDescriptionRef) -> i32 { self.insert_with_min_num(fd_ref, 0) } /// Insert a file description, giving it a file descriptor that is at least `min_fd_num`. - pub fn insert_with_min_num(&mut self, file_handle: FileDescriptionRef, min_fd_num: i32) -> i32 { + pub fn insert_with_min_num( + &mut self, + file_handle: DynFileDescriptionRef, + min_fd_num: i32, + ) -> i32 { // Find the lowest unused FD, starting from min_fd. If the first such unused FD is in // between used FDs, the find_map combinator will return it. If the first such unused FD // is after all other used FDs, the find_map combinator will return None, and we will use @@ -356,12 +389,12 @@ impl FdTable { new_fd_num } - pub fn get(&self, fd_num: i32) -> Option { + pub fn get(&self, fd_num: i32) -> Option { let fd = self.fds.get(&fd_num)?; Some(fd.clone()) } - pub fn remove(&mut self, fd_num: i32) -> Option { + pub fn remove(&mut self, fd_num: i32) -> Option { self.fds.remove(&fd_num) } diff --git a/src/tools/miri/src/shims/native_lib.rs b/src/tools/miri/src/shims/native_lib.rs index 87be5a521d13f..8c9e1860f31d9 100644 --- a/src/tools/miri/src/shims/native_lib.rs +++ b/src/tools/miri/src/shims/native_lib.rs @@ -72,7 +72,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { } // Functions with no declared return type (i.e., the default return) // have the output_type `Tuple([])`. - ty::Tuple(t_list) if t_list.len() == 0 => { + ty::Tuple(t_list) if t_list.is_empty() => { unsafe { ffi::call::<()>(ptr, libffi_args.as_slice()) }; return interp_ok(ImmTy::uninit(dest.layout)); } diff --git a/src/tools/miri/src/shims/time.rs b/src/tools/miri/src/shims/time.rs index 72d98bc1c4872..d6c77d9c4d9a0 100644 --- a/src/tools/miri/src/shims/time.rs +++ b/src/tools/miri/src/shims/time.rs @@ -331,8 +331,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { Some((TimeoutClock::Monotonic, TimeoutAnchor::Relative, duration)), callback!( @capture<'tcx> {} - @unblock = |_this| { panic!("sleeping thread unblocked before time is up") } - @timeout = |_this| { interp_ok(()) } + |_this, unblock: UnblockKind| { + assert_eq!(unblock, UnblockKind::TimedOut); + interp_ok(()) + } ), ); interp_ok(Scalar::from_i32(0)) @@ -353,8 +355,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { Some((TimeoutClock::Monotonic, TimeoutAnchor::Relative, duration)), callback!( @capture<'tcx> {} - @unblock = |_this| { panic!("sleeping thread unblocked before time is up") } - @timeout = |_this| { interp_ok(()) } + |_this, unblock: UnblockKind| { + assert_eq!(unblock, UnblockKind::TimedOut); + interp_ok(()) + } ), ); interp_ok(()) diff --git a/src/tools/miri/src/shims/unix/fd.rs b/src/tools/miri/src/shims/unix/fd.rs index e5dead1a26387..0b59490308b44 100644 --- a/src/tools/miri/src/shims/unix/fd.rs +++ b/src/tools/miri/src/shims/unix/fd.rs @@ -88,7 +88,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // If old_fd and new_fd point to the same description, then `dup_fd` ensures we keep the underlying file description alive. if let Some(old_new_fd) = this.machine.fds.fds.insert(new_fd_num, fd) { // Ignore close error (not interpreter's) according to dup2() doc. - old_new_fd.close(this.machine.communicate(), this)?.ok(); + old_new_fd.close_ref(this.machine.communicate(), this)?.ok(); } } interp_ok(Scalar::from_i32(new_fd_num)) @@ -122,7 +122,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { }; let result = fd.as_unix().flock(this.machine.communicate(), parsed_op)?; - drop(fd); // return `0` if flock is successful let result = result.map(|()| 0i32); interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?)) @@ -198,7 +197,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let Some(fd) = this.machine.fds.remove(fd_num) else { return this.set_last_error_and_return_i32(LibcError("EBADF")); }; - let result = fd.close(this.machine.communicate(), this)?; + let result = fd.close_ref(this.machine.communicate(), this)?; // return `0` if close is successful let result = result.map(|()| 0i32); interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?)) @@ -246,7 +245,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // `usize::MAX` because it is bounded by the host's `isize`. match offset { - None => fd.read(&fd, communicate, buf, count, dest, this)?, + None => fd.read(communicate, buf, count, dest, this)?, Some(offset) => { let Ok(offset) = u64::try_from(offset) else { return this.set_last_error_and_return(LibcError("EINVAL"), dest); @@ -286,7 +285,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { }; match offset { - None => fd.write(&fd, communicate, buf, count, dest, this)?, + None => fd.write(communicate, buf, count, dest, this)?, Some(offset) => { let Ok(offset) = u64::try_from(offset) else { return this.set_last_error_and_return(LibcError("EINVAL"), dest); diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs index f47a96b10fe58..3353cf2cc59d2 100644 --- a/src/tools/miri/src/shims/unix/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/foreign_items.rs @@ -109,56 +109,54 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let this = self.eval_context_mut(); // See `fn emulate_foreign_item_inner` in `shims/foreign_items.rs` for the general pattern. - #[rustfmt::skip] match link_name.as_str() { // Environment related shims "getenv" => { - let [name] = this.check_shim(abi, Conv::C , link_name, args)?; + let [name] = this.check_shim(abi, Conv::C, link_name, args)?; let result = this.getenv(name)?; this.write_pointer(result, dest)?; } "unsetenv" => { - let [name] = this.check_shim(abi, Conv::C , link_name, args)?; + let [name] = this.check_shim(abi, Conv::C, link_name, args)?; let result = this.unsetenv(name)?; this.write_scalar(result, dest)?; } "setenv" => { - let [name, value, overwrite] = this.check_shim(abi, Conv::C , link_name, args)?; + let [name, value, overwrite] = this.check_shim(abi, Conv::C, link_name, args)?; this.read_scalar(overwrite)?.to_i32()?; let result = this.setenv(name, value)?; this.write_scalar(result, dest)?; } "getcwd" => { - let [buf, size] = this.check_shim(abi, Conv::C , link_name, args)?; + let [buf, size] = this.check_shim(abi, Conv::C, link_name, args)?; let result = this.getcwd(buf, size)?; this.write_pointer(result, dest)?; } "chdir" => { - let [path] = this.check_shim(abi, Conv::C , link_name, args)?; + let [path] = this.check_shim(abi, Conv::C, link_name, args)?; let result = this.chdir(path)?; this.write_scalar(result, dest)?; } "getpid" => { - let [] = this.check_shim(abi, Conv::C , link_name, args)?; + let [] = this.check_shim(abi, Conv::C, link_name, args)?; let result = this.getpid()?; this.write_scalar(result, dest)?; } "sysconf" => { - let [val] = - this.check_shim(abi, Conv::C, link_name, args)?; + let [val] = this.check_shim(abi, Conv::C, link_name, args)?; let result = this.sysconf(val)?; this.write_scalar(result, dest)?; } // File descriptors "read" => { - let [fd, buf, count] = this.check_shim(abi, Conv::C , link_name, args)?; + let [fd, buf, count] = this.check_shim(abi, Conv::C, link_name, args)?; let fd = this.read_scalar(fd)?.to_i32()?; let buf = this.read_pointer(buf)?; let count = this.read_target_usize(count)?; this.read(fd, buf, count, None, dest)?; } "write" => { - let [fd, buf, n] = this.check_shim(abi, Conv::C , link_name, args)?; + let [fd, buf, n] = this.check_shim(abi, Conv::C, link_name, args)?; let fd = this.read_scalar(fd)?.to_i32()?; let buf = this.read_pointer(buf)?; let count = this.read_target_usize(n)?; @@ -166,7 +164,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write(fd, buf, count, None, dest)?; } "pread" => { - let [fd, buf, count, offset] = this.check_shim(abi, Conv::C , link_name, args)?; + let [fd, buf, count, offset] = this.check_shim(abi, Conv::C, link_name, args)?; let fd = this.read_scalar(fd)?.to_i32()?; let buf = this.read_pointer(buf)?; let count = this.read_target_usize(count)?; @@ -174,7 +172,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.read(fd, buf, count, Some(offset), dest)?; } "pwrite" => { - let [fd, buf, n, offset] = this.check_shim(abi, Conv::C , link_name, args)?; + let [fd, buf, n, offset] = this.check_shim(abi, Conv::C, link_name, args)?; let fd = this.read_scalar(fd)?.to_i32()?; let buf = this.read_pointer(buf)?; let count = this.read_target_usize(n)?; @@ -183,49 +181,51 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write(fd, buf, count, Some(offset), dest)?; } "pread64" => { - let [fd, buf, count, offset] = this.check_shim(abi, Conv::C , link_name, args)?; + let [fd, buf, count, offset] = this.check_shim(abi, Conv::C, link_name, args)?; let fd = this.read_scalar(fd)?.to_i32()?; let buf = this.read_pointer(buf)?; let count = this.read_target_usize(count)?; - let offset = this.read_scalar(offset)?.to_int(this.libc_ty_layout("off64_t").size)?; + let offset = + this.read_scalar(offset)?.to_int(this.libc_ty_layout("off64_t").size)?; this.read(fd, buf, count, Some(offset), dest)?; } "pwrite64" => { - let [fd, buf, n, offset] = this.check_shim(abi, Conv::C , link_name, args)?; + let [fd, buf, n, offset] = this.check_shim(abi, Conv::C, link_name, args)?; let fd = this.read_scalar(fd)?.to_i32()?; let buf = this.read_pointer(buf)?; let count = this.read_target_usize(n)?; - let offset = this.read_scalar(offset)?.to_int(this.libc_ty_layout("off64_t").size)?; + let offset = + this.read_scalar(offset)?.to_int(this.libc_ty_layout("off64_t").size)?; trace!("Called pwrite64({:?}, {:?}, {:?}, {:?})", fd, buf, count, offset); this.write(fd, buf, count, Some(offset), dest)?; } "close" => { - let [fd] = this.check_shim(abi, Conv::C , link_name, args)?; + let [fd] = this.check_shim(abi, Conv::C, link_name, args)?; let result = this.close(fd)?; this.write_scalar(result, dest)?; } "fcntl" => { // `fcntl` is variadic. The argument count is checked based on the first argument // in `this.fcntl()`, so we do not use `check_shim` here. - this.check_abi_and_shim_symbol_clash(abi, Conv::C , link_name)?; + this.check_abi_and_shim_symbol_clash(abi, Conv::C, link_name)?; let result = this.fcntl(args)?; this.write_scalar(result, dest)?; } "dup" => { - let [old_fd] = this.check_shim(abi, Conv::C , link_name, args)?; + let [old_fd] = this.check_shim(abi, Conv::C, link_name, args)?; let old_fd = this.read_scalar(old_fd)?.to_i32()?; let new_fd = this.dup(old_fd)?; this.write_scalar(new_fd, dest)?; } "dup2" => { - let [old_fd, new_fd] = this.check_shim(abi, Conv::C , link_name, args)?; + let [old_fd, new_fd] = this.check_shim(abi, Conv::C, link_name, args)?; let old_fd = this.read_scalar(old_fd)?.to_i32()?; let new_fd = this.read_scalar(new_fd)?.to_i32()?; let result = this.dup2(old_fd, new_fd)?; this.write_scalar(result, dest)?; } "flock" => { - let [fd, op] = this.check_shim(abi, Conv::C , link_name, args)?; + let [fd, op] = this.check_shim(abi, Conv::C, link_name, args)?; let fd = this.read_scalar(fd)?.to_i32()?; let op = this.read_scalar(op)?.to_i32()?; let result = this.flock(fd, op)?; @@ -234,48 +234,49 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // File and file system access "open" | "open64" => { - // `open` is variadic, the third argument is only present when the second argument has O_CREAT (or on linux O_TMPFILE, but miri doesn't support that) set - this.check_abi_and_shim_symbol_clash(abi, Conv::C , link_name)?; + // `open` is variadic, the third argument is only present when the second argument + // has O_CREAT (or on linux O_TMPFILE, but miri doesn't support that) set + this.check_abi_and_shim_symbol_clash(abi, Conv::C, link_name)?; let result = this.open(args)?; this.write_scalar(result, dest)?; } "unlink" => { - let [path] = this.check_shim(abi, Conv::C , link_name, args)?; + let [path] = this.check_shim(abi, Conv::C, link_name, args)?; let result = this.unlink(path)?; this.write_scalar(result, dest)?; } "symlink" => { - let [target, linkpath] = this.check_shim(abi, Conv::C , link_name, args)?; + let [target, linkpath] = this.check_shim(abi, Conv::C, link_name, args)?; let result = this.symlink(target, linkpath)?; this.write_scalar(result, dest)?; } "rename" => { - let [oldpath, newpath] = this.check_shim(abi, Conv::C , link_name, args)?; + let [oldpath, newpath] = this.check_shim(abi, Conv::C, link_name, args)?; let result = this.rename(oldpath, newpath)?; this.write_scalar(result, dest)?; } "mkdir" => { - let [path, mode] = this.check_shim(abi, Conv::C , link_name, args)?; + let [path, mode] = this.check_shim(abi, Conv::C, link_name, args)?; let result = this.mkdir(path, mode)?; this.write_scalar(result, dest)?; } "rmdir" => { - let [path] = this.check_shim(abi, Conv::C , link_name, args)?; + let [path] = this.check_shim(abi, Conv::C, link_name, args)?; let result = this.rmdir(path)?; this.write_scalar(result, dest)?; } "opendir" => { - let [name] = this.check_shim(abi, Conv::C , link_name, args)?; + let [name] = this.check_shim(abi, Conv::C, link_name, args)?; let result = this.opendir(name)?; this.write_scalar(result, dest)?; } "closedir" => { - let [dirp] = this.check_shim(abi, Conv::C , link_name, args)?; + let [dirp] = this.check_shim(abi, Conv::C, link_name, args)?; let result = this.closedir(dirp)?; this.write_scalar(result, dest)?; } "lseek64" => { - let [fd, offset, whence] = this.check_shim(abi, Conv::C , link_name, args)?; + let [fd, offset, whence] = this.check_shim(abi, Conv::C, link_name, args)?; let fd = this.read_scalar(fd)?.to_i32()?; let offset = this.read_scalar(offset)?.to_i64()?; let whence = this.read_scalar(whence)?.to_i32()?; @@ -283,7 +284,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(result, dest)?; } "lseek" => { - let [fd, offset, whence] = this.check_shim(abi, Conv::C , link_name, args)?; + let [fd, offset, whence] = this.check_shim(abi, Conv::C, link_name, args)?; let fd = this.read_scalar(fd)?.to_i32()?; let offset = this.read_scalar(offset)?.to_int(this.libc_ty_layout("off_t").size)?; let whence = this.read_scalar(whence)?.to_i32()?; @@ -291,39 +292,36 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(result, dest)?; } "ftruncate64" => { - let [fd, length] = - this.check_shim(abi, Conv::C , link_name, args)?; + let [fd, length] = this.check_shim(abi, Conv::C, link_name, args)?; let fd = this.read_scalar(fd)?.to_i32()?; let length = this.read_scalar(length)?.to_i64()?; let result = this.ftruncate64(fd, length.into())?; this.write_scalar(result, dest)?; } "ftruncate" => { - let [fd, length] = - this.check_shim(abi, Conv::C , link_name, args)?; + let [fd, length] = this.check_shim(abi, Conv::C, link_name, args)?; let fd = this.read_scalar(fd)?.to_i32()?; let length = this.read_scalar(length)?.to_int(this.libc_ty_layout("off_t").size)?; let result = this.ftruncate64(fd, length)?; this.write_scalar(result, dest)?; } "fsync" => { - let [fd] = this.check_shim(abi, Conv::C , link_name, args)?; + let [fd] = this.check_shim(abi, Conv::C, link_name, args)?; let result = this.fsync(fd)?; this.write_scalar(result, dest)?; } "fdatasync" => { - let [fd] = this.check_shim(abi, Conv::C , link_name, args)?; + let [fd] = this.check_shim(abi, Conv::C, link_name, args)?; let result = this.fdatasync(fd)?; this.write_scalar(result, dest)?; } "readlink" => { - let [pathname, buf, bufsize] = this.check_shim(abi, Conv::C , link_name, args)?; + let [pathname, buf, bufsize] = this.check_shim(abi, Conv::C, link_name, args)?; let result = this.readlink(pathname, buf, bufsize)?; this.write_scalar(Scalar::from_target_isize(result, this), dest)?; } "posix_fadvise" => { - let [fd, offset, len, advice] = - this.check_shim(abi, Conv::C , link_name, args)?; + let [fd, offset, len, advice] = this.check_shim(abi, Conv::C, link_name, args)?; this.read_scalar(fd)?.to_i32()?; this.read_target_isize(offset)?; this.read_target_isize(len)?; @@ -332,12 +330,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_null(dest)?; } "realpath" => { - let [path, resolved_path] = this.check_shim(abi, Conv::C , link_name, args)?; + let [path, resolved_path] = this.check_shim(abi, Conv::C, link_name, args)?; let result = this.realpath(path, resolved_path)?; this.write_scalar(result, dest)?; } "mkstemp" => { - let [template] = this.check_shim(abi, Conv::C , link_name, args)?; + let [template] = this.check_shim(abi, Conv::C, link_name, args)?; let result = this.mkstemp(template)?; this.write_scalar(result, dest)?; } @@ -345,63 +343,59 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Unnamed sockets and pipes "socketpair" => { let [domain, type_, protocol, sv] = - this.check_shim(abi, Conv::C , link_name, args)?; + this.check_shim(abi, Conv::C, link_name, args)?; let result = this.socketpair(domain, type_, protocol, sv)?; this.write_scalar(result, dest)?; } "pipe" => { - let [pipefd] = - this.check_shim(abi, Conv::C , link_name, args)?; + let [pipefd] = this.check_shim(abi, Conv::C, link_name, args)?; let result = this.pipe2(pipefd, /*flags*/ None)?; this.write_scalar(result, dest)?; } "pipe2" => { // Currently this function does not exist on all Unixes, e.g. on macOS. - if !matches!(&*this.tcx.sess.target.os, "linux" | "freebsd" | "solaris" | "illumos") { - throw_unsup_format!( - "`pipe2` is not supported on {}", - this.tcx.sess.target.os - ); + if !matches!(&*this.tcx.sess.target.os, "linux" | "freebsd" | "solaris" | "illumos") + { + throw_unsup_format!("`pipe2` is not supported on {}", this.tcx.sess.target.os); } - let [pipefd, flags] = - this.check_shim(abi, Conv::C , link_name, args)?; + let [pipefd, flags] = this.check_shim(abi, Conv::C, link_name, args)?; let result = this.pipe2(pipefd, Some(flags))?; this.write_scalar(result, dest)?; } // Time "gettimeofday" => { - let [tv, tz] = this.check_shim(abi, Conv::C , link_name, args)?; + let [tv, tz] = this.check_shim(abi, Conv::C, link_name, args)?; let result = this.gettimeofday(tv, tz)?; this.write_scalar(result, dest)?; } "localtime_r" => { - let [timep, result_op] = this.check_shim(abi, Conv::C , link_name, args)?; + let [timep, result_op] = this.check_shim(abi, Conv::C, link_name, args)?; let result = this.localtime_r(timep, result_op)?; this.write_pointer(result, dest)?; } "clock_gettime" => { - let [clk_id, tp] = - this.check_shim(abi, Conv::C , link_name, args)?; + let [clk_id, tp] = this.check_shim(abi, Conv::C, link_name, args)?; let result = this.clock_gettime(clk_id, tp)?; this.write_scalar(result, dest)?; } // Allocation "posix_memalign" => { - let [memptr, align, size] = this.check_shim(abi, Conv::C , link_name, args)?; + let [memptr, align, size] = this.check_shim(abi, Conv::C, link_name, args)?; let result = this.posix_memalign(memptr, align, size)?; this.write_scalar(result, dest)?; } "mmap" => { - let [addr, length, prot, flags, fd, offset] = this.check_shim(abi, Conv::C , link_name, args)?; + let [addr, length, prot, flags, fd, offset] = + this.check_shim(abi, Conv::C, link_name, args)?; let offset = this.read_scalar(offset)?.to_int(this.libc_ty_layout("off_t").size)?; let ptr = this.mmap(addr, length, prot, flags, fd, offset)?; this.write_scalar(ptr, dest)?; } "munmap" => { - let [addr, length] = this.check_shim(abi, Conv::C , link_name, args)?; + let [addr, length] = this.check_shim(abi, Conv::C, link_name, args)?; let result = this.munmap(addr, length)?; this.write_scalar(result, dest)?; } @@ -414,8 +408,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.tcx.sess.target.os ); } - let [ptr, nmemb, size] = - this.check_shim(abi, Conv::C , link_name, args)?; + let [ptr, nmemb, size] = this.check_shim(abi, Conv::C, link_name, args)?; let ptr = this.read_pointer(ptr)?; let nmemb = this.read_target_usize(nmemb)?; let size = this.read_target_usize(size)?; @@ -438,19 +431,20 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { "aligned_alloc" => { // This is a C11 function, we assume all Unixes have it. // (MSVC explicitly does not support this.) - let [align, size] = - this.check_shim(abi, Conv::C , link_name, args)?; + let [align, size] = this.check_shim(abi, Conv::C, link_name, args)?; let res = this.aligned_alloc(align, size)?; this.write_pointer(res, dest)?; } // Dynamic symbol loading "dlsym" => { - let [handle, symbol] = this.check_shim(abi, Conv::C , link_name, args)?; + let [handle, symbol] = this.check_shim(abi, Conv::C, link_name, args)?; this.read_target_usize(handle)?; let symbol = this.read_pointer(symbol)?; let name = this.read_c_str(symbol)?; - if let Ok(name) = str::from_utf8(name) && is_dyn_sym(name, &this.tcx.sess.target.os) { + if let Ok(name) = str::from_utf8(name) + && is_dyn_sym(name, &this.tcx.sess.target.os) + { let ptr = this.fn_ptr(FnVal::Other(DynSym::from_str(name))); this.write_pointer(ptr, dest)?; } else { @@ -460,7 +454,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Thread-local storage "pthread_key_create" => { - let [key, dtor] = this.check_shim(abi, Conv::C , link_name, args)?; + let [key, dtor] = this.check_shim(abi, Conv::C, link_name, args)?; let key_place = this.deref_pointer_as(key, this.libc_ty_layout("pthread_key_t"))?; let dtor = this.read_pointer(dtor)?; @@ -488,21 +482,21 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_null(dest)?; } "pthread_key_delete" => { - let [key] = this.check_shim(abi, Conv::C , link_name, args)?; + let [key] = this.check_shim(abi, Conv::C, link_name, args)?; let key = this.read_scalar(key)?.to_bits(key.layout.size)?; this.machine.tls.delete_tls_key(key)?; // Return success (0) this.write_null(dest)?; } "pthread_getspecific" => { - let [key] = this.check_shim(abi, Conv::C , link_name, args)?; + let [key] = this.check_shim(abi, Conv::C, link_name, args)?; let key = this.read_scalar(key)?.to_bits(key.layout.size)?; let active_thread = this.active_thread(); let ptr = this.machine.tls.load_tls(key, active_thread, this)?; this.write_scalar(ptr, dest)?; } "pthread_setspecific" => { - let [key, new_ptr] = this.check_shim(abi, Conv::C , link_name, args)?; + let [key, new_ptr] = this.check_shim(abi, Conv::C, link_name, args)?; let key = this.read_scalar(key)?.to_bits(key.layout.size)?; let active_thread = this.active_thread(); let new_data = this.read_scalar(new_ptr)?; @@ -514,151 +508,149 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Synchronization primitives "pthread_mutexattr_init" => { - let [attr] = this.check_shim(abi, Conv::C , link_name, args)?; + let [attr] = this.check_shim(abi, Conv::C, link_name, args)?; this.pthread_mutexattr_init(attr)?; this.write_null(dest)?; } "pthread_mutexattr_settype" => { - let [attr, kind] = this.check_shim(abi, Conv::C , link_name, args)?; + let [attr, kind] = this.check_shim(abi, Conv::C, link_name, args)?; let result = this.pthread_mutexattr_settype(attr, kind)?; this.write_scalar(result, dest)?; } "pthread_mutexattr_destroy" => { - let [attr] = this.check_shim(abi, Conv::C , link_name, args)?; + let [attr] = this.check_shim(abi, Conv::C, link_name, args)?; this.pthread_mutexattr_destroy(attr)?; this.write_null(dest)?; } "pthread_mutex_init" => { - let [mutex, attr] = this.check_shim(abi, Conv::C , link_name, args)?; + let [mutex, attr] = this.check_shim(abi, Conv::C, link_name, args)?; this.pthread_mutex_init(mutex, attr)?; this.write_null(dest)?; } "pthread_mutex_lock" => { - let [mutex] = this.check_shim(abi, Conv::C , link_name, args)?; + let [mutex] = this.check_shim(abi, Conv::C, link_name, args)?; this.pthread_mutex_lock(mutex, dest)?; } "pthread_mutex_trylock" => { - let [mutex] = this.check_shim(abi, Conv::C , link_name, args)?; + let [mutex] = this.check_shim(abi, Conv::C, link_name, args)?; let result = this.pthread_mutex_trylock(mutex)?; this.write_scalar(result, dest)?; } "pthread_mutex_unlock" => { - let [mutex] = this.check_shim(abi, Conv::C , link_name, args)?; + let [mutex] = this.check_shim(abi, Conv::C, link_name, args)?; let result = this.pthread_mutex_unlock(mutex)?; this.write_scalar(result, dest)?; } "pthread_mutex_destroy" => { - let [mutex] = this.check_shim(abi, Conv::C , link_name, args)?; + let [mutex] = this.check_shim(abi, Conv::C, link_name, args)?; this.pthread_mutex_destroy(mutex)?; this.write_int(0, dest)?; } "pthread_rwlock_rdlock" => { - let [rwlock] = this.check_shim(abi, Conv::C , link_name, args)?; + let [rwlock] = this.check_shim(abi, Conv::C, link_name, args)?; this.pthread_rwlock_rdlock(rwlock, dest)?; } "pthread_rwlock_tryrdlock" => { - let [rwlock] = this.check_shim(abi, Conv::C , link_name, args)?; + let [rwlock] = this.check_shim(abi, Conv::C, link_name, args)?; let result = this.pthread_rwlock_tryrdlock(rwlock)?; this.write_scalar(result, dest)?; } "pthread_rwlock_wrlock" => { - let [rwlock] = this.check_shim(abi, Conv::C , link_name, args)?; + let [rwlock] = this.check_shim(abi, Conv::C, link_name, args)?; this.pthread_rwlock_wrlock(rwlock, dest)?; } "pthread_rwlock_trywrlock" => { - let [rwlock] = this.check_shim(abi, Conv::C , link_name, args)?; + let [rwlock] = this.check_shim(abi, Conv::C, link_name, args)?; let result = this.pthread_rwlock_trywrlock(rwlock)?; this.write_scalar(result, dest)?; } "pthread_rwlock_unlock" => { - let [rwlock] = this.check_shim(abi, Conv::C , link_name, args)?; + let [rwlock] = this.check_shim(abi, Conv::C, link_name, args)?; this.pthread_rwlock_unlock(rwlock)?; this.write_null(dest)?; } "pthread_rwlock_destroy" => { - let [rwlock] = this.check_shim(abi, Conv::C , link_name, args)?; + let [rwlock] = this.check_shim(abi, Conv::C, link_name, args)?; this.pthread_rwlock_destroy(rwlock)?; this.write_null(dest)?; } "pthread_condattr_init" => { - let [attr] = this.check_shim(abi, Conv::C , link_name, args)?; + let [attr] = this.check_shim(abi, Conv::C, link_name, args)?; this.pthread_condattr_init(attr)?; this.write_null(dest)?; } "pthread_condattr_setclock" => { - let [attr, clock_id] = - this.check_shim(abi, Conv::C , link_name, args)?; + let [attr, clock_id] = this.check_shim(abi, Conv::C, link_name, args)?; let result = this.pthread_condattr_setclock(attr, clock_id)?; this.write_scalar(result, dest)?; } "pthread_condattr_getclock" => { - let [attr, clock_id] = - this.check_shim(abi, Conv::C , link_name, args)?; + let [attr, clock_id] = this.check_shim(abi, Conv::C, link_name, args)?; this.pthread_condattr_getclock(attr, clock_id)?; this.write_null(dest)?; } "pthread_condattr_destroy" => { - let [attr] = this.check_shim(abi, Conv::C , link_name, args)?; + let [attr] = this.check_shim(abi, Conv::C, link_name, args)?; this.pthread_condattr_destroy(attr)?; this.write_null(dest)?; } "pthread_cond_init" => { - let [cond, attr] = this.check_shim(abi, Conv::C , link_name, args)?; + let [cond, attr] = this.check_shim(abi, Conv::C, link_name, args)?; this.pthread_cond_init(cond, attr)?; this.write_null(dest)?; } "pthread_cond_signal" => { - let [cond] = this.check_shim(abi, Conv::C , link_name, args)?; + let [cond] = this.check_shim(abi, Conv::C, link_name, args)?; this.pthread_cond_signal(cond)?; this.write_null(dest)?; } "pthread_cond_broadcast" => { - let [cond] = this.check_shim(abi, Conv::C , link_name, args)?; + let [cond] = this.check_shim(abi, Conv::C, link_name, args)?; this.pthread_cond_broadcast(cond)?; this.write_null(dest)?; } "pthread_cond_wait" => { - let [cond, mutex] = this.check_shim(abi, Conv::C , link_name, args)?; + let [cond, mutex] = this.check_shim(abi, Conv::C, link_name, args)?; this.pthread_cond_wait(cond, mutex, dest)?; } "pthread_cond_timedwait" => { - let [cond, mutex, abstime] = this.check_shim(abi, Conv::C , link_name, args)?; + let [cond, mutex, abstime] = this.check_shim(abi, Conv::C, link_name, args)?; this.pthread_cond_timedwait(cond, mutex, abstime, dest)?; } "pthread_cond_destroy" => { - let [cond] = this.check_shim(abi, Conv::C , link_name, args)?; + let [cond] = this.check_shim(abi, Conv::C, link_name, args)?; this.pthread_cond_destroy(cond)?; this.write_null(dest)?; } // Threading "pthread_create" => { - let [thread, attr, start, arg] = this.check_shim(abi, Conv::C , link_name, args)?; + let [thread, attr, start, arg] = this.check_shim(abi, Conv::C, link_name, args)?; this.pthread_create(thread, attr, start, arg)?; this.write_null(dest)?; } "pthread_join" => { - let [thread, retval] = this.check_shim(abi, Conv::C , link_name, args)?; + let [thread, retval] = this.check_shim(abi, Conv::C, link_name, args)?; let res = this.pthread_join(thread, retval)?; this.write_scalar(res, dest)?; } "pthread_detach" => { - let [thread] = this.check_shim(abi, Conv::C , link_name, args)?; + let [thread] = this.check_shim(abi, Conv::C, link_name, args)?; let res = this.pthread_detach(thread)?; this.write_scalar(res, dest)?; } "pthread_self" => { - let [] = this.check_shim(abi, Conv::C , link_name, args)?; + let [] = this.check_shim(abi, Conv::C, link_name, args)?; let res = this.pthread_self()?; this.write_scalar(res, dest)?; } "sched_yield" => { - let [] = this.check_shim(abi, Conv::C , link_name, args)?; + let [] = this.check_shim(abi, Conv::C, link_name, args)?; this.sched_yield()?; this.write_null(dest)?; } "nanosleep" => { - let [req, rem] = this.check_shim(abi, Conv::C , link_name, args)?; + let [req, rem] = this.check_shim(abi, Conv::C, link_name, args)?; let result = this.nanosleep(req, rem)?; this.write_scalar(result, dest)?; } @@ -671,8 +663,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { ); } - let [pid, cpusetsize, mask] = - this.check_shim(abi, Conv::C , link_name, args)?; + let [pid, cpusetsize, mask] = this.check_shim(abi, Conv::C, link_name, args)?; let pid = this.read_scalar(pid)?.to_u32()?; let cpusetsize = this.read_target_usize(cpusetsize)?; let mask = this.read_pointer(mask)?; @@ -680,7 +671,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // TODO: when https://github.com/rust-lang/miri/issues/3730 is fixed this should use its notion of tid/pid let thread_id = match pid { 0 => this.active_thread(), - _ => throw_unsup_format!("`sched_getaffinity` is only supported with a pid of 0 (indicating the current thread)"), + _ => + throw_unsup_format!( + "`sched_getaffinity` is only supported with a pid of 0 (indicating the current thread)" + ), }; // The mask is stored in chunks, and the size must be a whole number of chunks. @@ -694,7 +688,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } else if let Some(cpuset) = this.machine.thread_cpu_affinity.get(&thread_id) { let cpuset = cpuset.clone(); // we only copy whole chunks of size_of::() - let byte_count = Ord::min(cpuset.as_slice().len(), cpusetsize.try_into().unwrap()); + let byte_count = + Ord::min(cpuset.as_slice().len(), cpusetsize.try_into().unwrap()); this.write_bytes_ptr(mask, cpuset.as_slice()[..byte_count].iter().copied())?; this.write_null(dest)?; } else { @@ -711,8 +706,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { ); } - let [pid, cpusetsize, mask] = - this.check_shim(abi, Conv::C , link_name, args)?; + let [pid, cpusetsize, mask] = this.check_shim(abi, Conv::C, link_name, args)?; let pid = this.read_scalar(pid)?.to_u32()?; let cpusetsize = this.read_target_usize(cpusetsize)?; let mask = this.read_pointer(mask)?; @@ -720,7 +714,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // TODO: when https://github.com/rust-lang/miri/issues/3730 is fixed this should use its notion of tid/pid let thread_id = match pid { 0 => this.active_thread(), - _ => throw_unsup_format!("`sched_setaffinity` is only supported with a pid of 0 (indicating the current thread)"), + _ => + throw_unsup_format!( + "`sched_setaffinity` is only supported with a pid of 0 (indicating the current thread)" + ), }; if this.ptr_is_null(mask)? { @@ -729,7 +726,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // NOTE: cpusetsize might be smaller than `CpuAffinityMask::CPU_MASK_BYTES`. // Any unspecified bytes are treated as zero here (none of the CPUs are configured). // This is not exactly documented, so we assume that this is the behavior in practice. - let bits_slice = this.read_bytes_ptr_strip_provenance(mask, Size::from_bytes(cpusetsize))?; + let bits_slice = + this.read_bytes_ptr_strip_provenance(mask, Size::from_bytes(cpusetsize))?; // This ignores the bytes beyond `CpuAffinityMask::CPU_MASK_BYTES` let bits_array: [u8; CpuAffinityMask::CPU_MASK_BYTES] = std::array::from_fn(|i| bits_slice.get(i).copied().unwrap_or(0)); @@ -748,12 +746,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Miscellaneous "isatty" => { - let [fd] = this.check_shim(abi, Conv::C , link_name, args)?; + let [fd] = this.check_shim(abi, Conv::C, link_name, args)?; let result = this.isatty(fd)?; this.write_scalar(result, dest)?; } "pthread_atfork" => { - let [prepare, parent, child] = this.check_shim(abi, Conv::C , link_name, args)?; + let [prepare, parent, child] = this.check_shim(abi, Conv::C, link_name, args)?; this.read_pointer(prepare)?; this.read_pointer(parent)?; this.read_pointer(child)?; @@ -763,15 +761,17 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { "getentropy" => { // This function is non-standard but exists with the same signature and behavior on // Linux, macOS, FreeBSD and Solaris/Illumos. - if !matches!(&*this.tcx.sess.target.os, "linux" | "macos" | "freebsd" | "illumos" | "solaris" | "android") { + if !matches!( + &*this.tcx.sess.target.os, + "linux" | "macos" | "freebsd" | "illumos" | "solaris" | "android" + ) { throw_unsup_format!( "`getentropy` is not supported on {}", this.tcx.sess.target.os ); } - let [buf, bufsize] = - this.check_shim(abi, Conv::C , link_name, args)?; + let [buf, bufsize] = this.check_shim(abi, Conv::C, link_name, args)?; let buf = this.read_pointer(buf)?; let bufsize = this.read_target_usize(bufsize)?; @@ -789,8 +789,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } "strerror_r" => { - let [errnum, buf, buflen] = - this.check_shim(abi, Conv::C, link_name, args)?; + let [errnum, buf, buflen] = this.check_shim(abi, Conv::C, link_name, args)?; let result = this.strerror_r(errnum, buf, buflen)?; this.write_scalar(result, dest)?; } @@ -798,14 +797,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { "getrandom" => { // This function is non-standard but exists with the same signature and behavior on // Linux, FreeBSD and Solaris/Illumos. - if !matches!(&*this.tcx.sess.target.os, "linux" | "freebsd" | "illumos" | "solaris" | "android") { + if !matches!( + &*this.tcx.sess.target.os, + "linux" | "freebsd" | "illumos" | "solaris" | "android" + ) { throw_unsup_format!( "`getrandom` is not supported on {}", this.tcx.sess.target.os ); } - let [ptr, len, flags] = - this.check_shim(abi, Conv::C , link_name, args)?; + let [ptr, len, flags] = this.check_shim(abi, Conv::C, link_name, args)?; let ptr = this.read_pointer(ptr)?; let len = this.read_target_usize(len)?; let _flags = this.read_scalar(flags)?.to_i32()?; @@ -822,7 +823,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.tcx.sess.target.os ); } - let [ptr, len] = this.check_shim(abi, Conv::C , link_name, args)?; + let [ptr, len] = this.check_shim(abi, Conv::C, link_name, args)?; let ptr = this.read_pointer(ptr)?; let len = this.read_target_usize(len)?; this.gen_random(ptr, len)?; @@ -841,7 +842,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // For arm32 they did something custom, but similar enough that the same // `_Unwind_RaiseException` impl in miri should work: // https://github.com/ARM-software/abi-aa/blob/main/ehabi32/ehabi32.rst - if !matches!(&*this.tcx.sess.target.os, "linux" | "freebsd" | "illumos" | "solaris" | "android" | "macos") { + if !matches!( + &*this.tcx.sess.target.os, + "linux" | "freebsd" | "illumos" | "solaris" | "android" | "macos" + ) { throw_unsup_format!( "`_Unwind_RaiseException` is not supported on {}", this.tcx.sess.target.os @@ -853,43 +857,42 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return interp_ok(EmulateItemResult::NeedsUnwind); } "getuid" => { - let [] = this.check_shim(abi, Conv::C , link_name, args)?; + let [] = this.check_shim(abi, Conv::C, link_name, args)?; // For now, just pretend we always have this fixed UID. this.write_int(UID, dest)?; } // Incomplete shims that we "stub out" just to get pre-main initialization code to work. // These shims are enabled only when the caller is in the standard library. - "pthread_attr_getguardsize" - if this.frame_in_std() => { - let [_attr, guard_size] = this.check_shim(abi, Conv::C , link_name, args)?; + "pthread_attr_getguardsize" if this.frame_in_std() => { + let [_attr, guard_size] = this.check_shim(abi, Conv::C, link_name, args)?; let guard_size = this.deref_pointer(guard_size)?; let guard_size_layout = this.libc_ty_layout("size_t"); - this.write_scalar(Scalar::from_uint(this.machine.page_size, guard_size_layout.size), &guard_size)?; + this.write_scalar( + Scalar::from_uint(this.machine.page_size, guard_size_layout.size), + &guard_size, + )?; // Return success (`0`). this.write_null(dest)?; } - | "pthread_attr_init" - | "pthread_attr_destroy" - if this.frame_in_std() => { - let [_] = this.check_shim(abi, Conv::C , link_name, args)?; + "pthread_attr_init" | "pthread_attr_destroy" if this.frame_in_std() => { + let [_] = this.check_shim(abi, Conv::C, link_name, args)?; this.write_null(dest)?; } - | "pthread_attr_setstacksize" - if this.frame_in_std() => { - let [_, _] = this.check_shim(abi, Conv::C , link_name, args)?; + "pthread_attr_setstacksize" if this.frame_in_std() => { + let [_, _] = this.check_shim(abi, Conv::C, link_name, args)?; this.write_null(dest)?; } - "pthread_attr_getstack" - if this.frame_in_std() => { + "pthread_attr_getstack" if this.frame_in_std() => { // We don't support "pthread_attr_setstack", so we just pretend all stacks have the same values here. // Hence we can mostly ignore the input `attr_place`. let [attr_place, addr_place, size_place] = - this.check_shim(abi, Conv::C , link_name, args)?; - let _attr_place = this.deref_pointer_as(attr_place, this.libc_ty_layout("pthread_attr_t"))?; + this.check_shim(abi, Conv::C, link_name, args)?; + let _attr_place = + this.deref_pointer_as(attr_place, this.libc_ty_layout("pthread_attr_t"))?; let addr_place = this.deref_pointer(addr_place)?; let size_place = this.deref_pointer(size_place)?; @@ -906,24 +909,19 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_null(dest)?; } - | "signal" - | "sigaltstack" - if this.frame_in_std() => { - let [_, _] = this.check_shim(abi, Conv::C , link_name, args)?; + "signal" | "sigaltstack" if this.frame_in_std() => { + let [_, _] = this.check_shim(abi, Conv::C, link_name, args)?; this.write_null(dest)?; } - | "sigaction" - | "mprotect" - if this.frame_in_std() => { - let [_, _, _] = this.check_shim(abi, Conv::C , link_name, args)?; + "sigaction" | "mprotect" if this.frame_in_std() => { + let [_, _, _] = this.check_shim(abi, Conv::C, link_name, args)?; this.write_null(dest)?; } - "getpwuid_r" | "__posix_getpwuid_r" - if this.frame_in_std() => { + "getpwuid_r" | "__posix_getpwuid_r" if this.frame_in_std() => { // getpwuid_r is the standard name, __posix_getpwuid_r is used on solarish let [uid, pwd, buf, buflen, result] = - this.check_shim(abi, Conv::C , link_name, args)?; + this.check_shim(abi, Conv::C, link_name, args)?; this.check_no_isolation("`getpwuid_r`")?; let uid = this.read_scalar(uid)?.to_u32()?; @@ -961,11 +959,26 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { _ => { let target_os = &*this.tcx.sess.target.os; return match target_os { - "android" => android::EvalContextExt::emulate_foreign_item_inner(this, link_name, abi, args, dest), - "freebsd" => freebsd::EvalContextExt::emulate_foreign_item_inner(this, link_name, abi, args, dest), - "linux" => linux::EvalContextExt::emulate_foreign_item_inner(this, link_name, abi, args, dest), - "macos" => macos::EvalContextExt::emulate_foreign_item_inner(this, link_name, abi, args, dest), - "solaris" | "illumos" => solarish::EvalContextExt::emulate_foreign_item_inner(this, link_name, abi, args, dest), + "android" => + android::EvalContextExt::emulate_foreign_item_inner( + this, link_name, abi, args, dest, + ), + "freebsd" => + freebsd::EvalContextExt::emulate_foreign_item_inner( + this, link_name, abi, args, dest, + ), + "linux" => + linux::EvalContextExt::emulate_foreign_item_inner( + this, link_name, abi, args, dest, + ), + "macos" => + macos::EvalContextExt::emulate_foreign_item_inner( + this, link_name, abi, args, dest, + ), + "solaris" | "illumos" => + solarish::EvalContextExt::emulate_foreign_item_inner( + this, link_name, abi, args, dest, + ), _ => interp_ok(EmulateItemResult::NotSupported), }; } diff --git a/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs b/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs index 5381234e28ca7..03dbd931329c7 100644 --- a/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs @@ -21,29 +21,38 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let this = self.eval_context_mut(); match link_name.as_str() { // Threading - "pthread_set_name_np" => { + "pthread_setname_np" => { let [thread, name] = this.check_shim(abi, Conv::C, link_name, args)?; let max_len = usize::MAX; // FreeBSD does not seem to have a limit. - // FreeBSD's pthread_set_name_np does not return anything. - this.pthread_setname_np( + let res = match this.pthread_setname_np( this.read_scalar(thread)?, this.read_scalar(name)?, max_len, /* truncate */ false, - )?; + )? { + ThreadNameResult::Ok => Scalar::from_u32(0), + ThreadNameResult::NameTooLong => unreachable!(), + ThreadNameResult::ThreadNotFound => this.eval_libc("ESRCH"), + }; + this.write_scalar(res, dest)?; } - "pthread_get_name_np" => { + "pthread_getname_np" => { let [thread, name, len] = this.check_shim(abi, Conv::C, link_name, args)?; - // FreeBSD's pthread_get_name_np does not return anything - // and uses strlcpy, which truncates the resulting value, + // FreeBSD's pthread_getname_np uses strlcpy, which truncates the resulting value, // but always adds a null terminator (except for zero-sized buffers). // https://github.com/freebsd/freebsd-src/blob/c2d93a803acef634bd0eede6673aeea59e90c277/lib/libthr/thread/thr_info.c#L119-L144 - this.pthread_getname_np( + let res = match this.pthread_getname_np( this.read_scalar(thread)?, this.read_scalar(name)?, this.read_scalar(len)?, /* truncate */ true, - )?; + )? { + ThreadNameResult::Ok => Scalar::from_u32(0), + // `NameTooLong` is possible when the buffer is zero sized, + ThreadNameResult::NameTooLong => Scalar::from_u32(0), + ThreadNameResult::ThreadNotFound => this.eval_libc("ESRCH"), + }; + this.write_scalar(res, dest)?; } // File related shims diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index 5682fb659e7a1..25594b7803184 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -31,8 +31,7 @@ impl FileDescription for FileHandle { } fn read<'tcx>( - &self, - _self_ref: &FileDescriptionRef, + self: FileDescriptionRef, communicate_allowed: bool, ptr: Pointer, len: usize, @@ -49,8 +48,7 @@ impl FileDescription for FileHandle { } fn write<'tcx>( - &self, - _self_ref: &FileDescriptionRef, + self: FileDescriptionRef, communicate_allowed: bool, ptr: Pointer, len: usize, @@ -76,7 +74,7 @@ impl FileDescription for FileHandle { } fn close<'tcx>( - self: Box, + self, communicate_allowed: bool, _ecx: &mut MiriInterpCx<'tcx>, ) -> InterpResult<'tcx, io::Result<()>> { @@ -87,7 +85,7 @@ impl FileDescription for FileHandle { // to handle possible errors correctly. let result = self.file.sync_all(); // Now we actually close the file and return the result. - drop(*self); + drop(self.file); interp_ok(result) } else { // We drop the file, this closes it but ignores any errors @@ -96,7 +94,7 @@ impl FileDescription for FileHandle { // `/dev/urandom` which are read-only. Check // https://github.com/rust-lang/miri/issues/999#issuecomment-568920439 // for a deeper discussion. - drop(*self); + drop(self.file); interp_ok(Ok(())) } } @@ -1311,22 +1309,19 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { }; // FIXME: Support ftruncate64 for all FDs - let FileHandle { file, writable } = fd.downcast::().ok_or_else(|| { + let file = fd.downcast::().ok_or_else(|| { err_unsup_format!("`ftruncate64` is only supported on file-backed file descriptors") })?; - if *writable { + if file.writable { if let Ok(length) = length.try_into() { - let result = file.set_len(length); - drop(fd); + let result = file.file.set_len(length); let result = this.try_unwrap_io_result(result.map(|_| 0i32))?; interp_ok(Scalar::from_i32(result)) } else { - drop(fd); this.set_last_error_and_return_i32(LibcError("EINVAL")) } } else { - drop(fd); // The file is not writable this.set_last_error_and_return_i32(LibcError("EINVAL")) } @@ -1358,11 +1353,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return this.set_last_error_and_return_i32(LibcError("EBADF")); }; // Only regular files support synchronization. - let FileHandle { file, writable } = fd.downcast::().ok_or_else(|| { + let file = fd.downcast::().ok_or_else(|| { err_unsup_format!("`fsync` is only supported on file-backed file descriptors") })?; - let io_result = maybe_sync_file(file, *writable, File::sync_all); - drop(fd); + let io_result = maybe_sync_file(&file.file, file.writable, File::sync_all); interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?)) } @@ -1382,11 +1376,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return this.set_last_error_and_return_i32(LibcError("EBADF")); }; // Only regular files support synchronization. - let FileHandle { file, writable } = fd.downcast::().ok_or_else(|| { + let file = fd.downcast::().ok_or_else(|| { err_unsup_format!("`fdatasync` is only supported on file-backed file descriptors") })?; - let io_result = maybe_sync_file(file, *writable, File::sync_data); - drop(fd); + let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data); interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?)) } @@ -1425,11 +1418,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return this.set_last_error_and_return_i32(LibcError("EBADF")); }; // Only regular files support synchronization. - let FileHandle { file, writable } = fd.downcast::().ok_or_else(|| { + let file = fd.downcast::().ok_or_else(|| { err_unsup_format!("`sync_data_range` is only supported on file-backed file descriptors") })?; - let io_result = maybe_sync_file(file, *writable, File::sync_data); - drop(fd); + let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data); interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?)) } 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 5b240351c2058..de8bcb54aef5b 100644 --- a/src/tools/miri/src/shims/unix/linux_like/epoll.rs +++ b/src/tools/miri/src/shims/unix/linux_like/epoll.rs @@ -5,12 +5,14 @@ use std::rc::{Rc, Weak}; use std::time::Duration; use crate::concurrency::VClock; -use crate::shims::files::{FdId, FileDescription, FileDescriptionRef, WeakFileDescriptionRef}; +use crate::shims::files::{ + DynFileDescriptionRef, FdId, FileDescription, FileDescriptionRef, WeakFileDescriptionRef, +}; use crate::shims::unix::UnixFileDescription; use crate::*; /// An `Epoll` file descriptor connects file handles and epoll events -#[derive(Clone, Debug, Default)] +#[derive(Debug, Default)] struct Epoll { /// A map of EpollEventInterests registered under this epoll instance. /// Each entry is differentiated using FdId and file descriptor value. @@ -18,11 +20,15 @@ struct Epoll { /// A map of EpollEventInstance that will be returned when `epoll_wait` is called. /// Similar to interest_list, the entry is also differentiated using FdId /// and file descriptor value. - // This is an Rc because EpollInterest need to hold a reference to update - // it. - ready_list: Rc, + ready_list: ReadyList, /// A list of thread ids blocked on this epoll instance. - thread_id: RefCell>, + blocked_tid: RefCell>, +} + +impl VisitProvenance for Epoll { + fn visit_provenance(&self, _visit: &mut VisitWith<'_>) { + // No provenance anywhere in this type. + } } /// EpollEventInstance contains information that will be returned by epoll_wait. @@ -51,7 +57,7 @@ impl EpollEventInstance { /// see the man page: /// /// -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct EpollEventInterest { /// The file descriptor value of the file description registered. /// This is only used for ready_list, to inform userspace which FD triggered an event. @@ -65,10 +71,10 @@ pub struct EpollEventInterest { /// but only u64 is supported for now. /// data: u64, - /// Ready list of the epoll instance under which this EpollEventInterest is registered. - ready_list: Rc, /// The epoll file description that this EpollEventInterest is registered under. - weak_epfd: WeakFileDescriptionRef, + /// This is weak to avoid cycles, but an upgrade is always guaranteed to succeed + /// because only the `Epoll` holds a strong ref to a `EpollEventInterest`. + weak_epfd: WeakFileDescriptionRef, } /// EpollReadyEvents reflects the readiness of a file description. @@ -134,19 +140,13 @@ impl EpollReadyEvents { } } -impl Epoll { - fn get_ready_list(&self) -> Rc { - Rc::clone(&self.ready_list) - } -} - impl FileDescription for Epoll { fn name(&self) -> &'static str { "epoll" } fn close<'tcx>( - self: Box, + self, _communicate_allowed: bool, _ecx: &mut MiriInterpCx<'tcx>, ) -> InterpResult<'tcx, io::Result<()>> { @@ -271,17 +271,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let Some(epfd) = this.machine.fds.get(epfd_value) else { return this.set_last_error_and_return_i32(LibcError("EBADF")); }; - let epoll_file_description = epfd + let epfd = epfd .downcast::() .ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_ctl`"))?; - let mut interest_list = epoll_file_description.interest_list.borrow_mut(); - let ready_list = &epoll_file_description.ready_list; + let mut interest_list = epfd.interest_list.borrow_mut(); let Some(fd_ref) = this.machine.fds.get(fd) else { return this.set_last_error_and_return_i32(LibcError("EBADF")); }; - let id = fd_ref.get_id(); + let id = fd_ref.id(); if op == epoll_ctl_add || op == epoll_ctl_mod { // Read event bitmask and data from epoll_event passed by caller. @@ -337,30 +336,33 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } } - // Create an epoll_interest. - let interest = Rc::new(RefCell::new(EpollEventInterest { - fd_num: fd, - events, - data, - ready_list: Rc::clone(ready_list), - weak_epfd: epfd.downgrade(), - })); - if op == epoll_ctl_add { + // Create an epoll_interest. + let interest = Rc::new(RefCell::new(EpollEventInterest { + fd_num: fd, + events, + data, + weak_epfd: FileDescriptionRef::downgrade(&epfd), + })); + // Notification will be returned for current epfd if there is event in the file + // descriptor we registered. + check_and_update_one_event_interest(&fd_ref, &interest, id, this)?; + // Insert an epoll_interest to global epoll_interest list. this.machine.epoll_interests.insert_epoll_interest(id, Rc::downgrade(&interest)); - interest_list.insert(epoll_key, Rc::clone(&interest)); + interest_list.insert(epoll_key, interest); } else { - // Directly modify the epoll_interest so the global epoll_event_interest table - // will be updated too. - let mut epoll_interest = interest_list.get_mut(&epoll_key).unwrap().borrow_mut(); - epoll_interest.events = events; - epoll_interest.data = data; + // Modify the existing interest. + let epoll_interest = interest_list.get_mut(&epoll_key).unwrap(); + { + let mut epoll_interest = epoll_interest.borrow_mut(); + epoll_interest.events = events; + epoll_interest.data = data; + } + // Updating an FD interest triggers events. + check_and_update_one_event_interest(&fd_ref, epoll_interest, id, this)?; } - // Notification will be returned for current epfd if there is event in the file - // descriptor we registered. - check_and_update_one_event_interest(&fd_ref, interest, id, this)?; interp_ok(Scalar::from_i32(0)) } else if op == epoll_ctl_del { let epoll_key = (id, fd); @@ -373,7 +375,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { drop(epoll_interest); // Remove related epoll_interest from ready list. - ready_list.mapping.borrow_mut().remove(&epoll_key); + epfd.ready_list.mapping.borrow_mut().remove(&epoll_key); // Remove dangling EpollEventInterest from its global table. // .unwrap() below should succeed because the file description id must have registered @@ -452,24 +454,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let Some(epfd) = this.machine.fds.get(epfd_value) else { return this.set_last_error_and_return(LibcError("EBADF"), dest); }; - // Create a weak ref of epfd and pass it to callback so we will make sure that epfd - // is not close after the thread unblocks. - let weak_epfd = epfd.downgrade(); + let Some(epfd) = epfd.downcast::() else { + return this.set_last_error_and_return(LibcError("EBADF"), dest); + }; // We just need to know if the ready list is empty and borrow the thread_ids out. - // The whole logic is wrapped inside a block so we don't need to manually drop epfd later. - let ready_list_empty; - let mut thread_ids; - { - let epoll_file_description = epfd - .downcast::() - .ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_wait`"))?; - ready_list_empty = epoll_file_description.ready_list.mapping.borrow().is_empty(); - thread_ids = epoll_file_description.thread_id.borrow_mut(); - } + let ready_list_empty = epfd.ready_list.mapping.borrow().is_empty(); if timeout == 0 || !ready_list_empty { // If the ready list is not empty, or the timeout is 0, we can return immediately. - return_ready_list(epfd_value, weak_epfd, dest, &event, this)?; + return_ready_list(&epfd, dest, &event, this)?; } else { // Blocking let timeout = match timeout { @@ -484,34 +477,37 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { ); } }; - thread_ids.push(this.active_thread()); + // Record this thread as blocked. + epfd.blocked_tid.borrow_mut().push(this.active_thread()); + // And block it. let dest = dest.clone(); + // We keep a strong ref to the underlying `Epoll` to make sure it sticks around. + // This means there'll be a leak if we never wake up, but that anyway would imply + // a thread is permanently blocked so this is fine. this.block_thread( BlockReason::Epoll, timeout, callback!( @capture<'tcx> { - epfd_value: i32, - weak_epfd: WeakFileDescriptionRef, + epfd: FileDescriptionRef, dest: MPlaceTy<'tcx>, event: MPlaceTy<'tcx>, } - @unblock = |this| { - return_ready_list(epfd_value, weak_epfd, &dest, &event, this)?; - interp_ok(()) - } - @timeout = |this| { - // No notification after blocking timeout. - let Some(epfd) = weak_epfd.upgrade() else { - throw_unsup_format!("epoll FD {epfd_value} got closed while blocking.") - }; - // Remove the current active thread_id from the blocked thread_id list. - epfd.downcast::() - .ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_wait`"))? - .thread_id.borrow_mut() - .retain(|&id| id != this.active_thread()); - this.write_int(0, &dest)?; - interp_ok(()) + |this, unblock: UnblockKind| { + match unblock { + UnblockKind::Ready => { + return_ready_list(&epfd, &dest, &event, this)?; + interp_ok(()) + }, + UnblockKind::TimedOut => { + // Remove the current active thread_id from the blocked thread_id list. + epfd + .blocked_tid.borrow_mut() + .retain(|&id| id != this.active_thread()); + this.write_int(0, &dest)?; + interp_ok(()) + }, + } } ), ); @@ -528,21 +524,17 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { /// do not call this function when an FD didn't have anything happen to it! fn check_and_update_readiness( &mut self, - fd_ref: &FileDescriptionRef, + fd_ref: DynFileDescriptionRef, ) -> InterpResult<'tcx, ()> { let this = self.eval_context_mut(); - let id = fd_ref.get_id(); + let id = fd_ref.id(); let mut waiter = Vec::new(); // Get a list of EpollEventInterest that is associated to a specific file description. if let Some(epoll_interests) = this.machine.epoll_interests.get_epoll_interest(id) { for weak_epoll_interest in epoll_interests { if let Some(epoll_interest) = weak_epoll_interest.upgrade() { - let is_updated = check_and_update_one_event_interest( - fd_ref, - epoll_interest.clone(), - id, - this, - )?; + let is_updated = + check_and_update_one_event_interest(&fd_ref, &epoll_interest, id, this)?; if is_updated { // Edge-triggered notification only notify one thread even if there are // multiple threads blocked on the same epfd. @@ -553,10 +545,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // holds a strong ref to epoll_interest. let epfd = epoll_interest.borrow().weak_epfd.upgrade().unwrap(); // FIXME: We can randomly pick a thread to unblock. - - let epoll = epfd.downcast::().unwrap(); - - if let Some(thread_id) = epoll.thread_id.borrow_mut().pop() { + if let Some(thread_id) = epfd.blocked_tid.borrow_mut().pop() { waiter.push(thread_id); }; } @@ -595,14 +584,15 @@ fn ready_list_next( /// notification was added/updated. Unlike check_and_update_readiness, this function sends a /// notification to only one epoll instance. fn check_and_update_one_event_interest<'tcx>( - fd_ref: &FileDescriptionRef, - interest: Rc>, + fd_ref: &DynFileDescriptionRef, + interest: &RefCell, id: FdId, ecx: &MiriInterpCx<'tcx>, ) -> InterpResult<'tcx, bool> { // Get the bitmask of ready events for a file description. let ready_events_bitmask = fd_ref.as_unix().get_epoll_ready_events()?.get_event_bitmask(ecx); let epoll_event_interest = interest.borrow(); + let epfd = epoll_event_interest.weak_epfd.upgrade().unwrap(); // This checks if any of the events specified in epoll_event_interest.events // match those in ready_events. let flags = epoll_event_interest.events & ready_events_bitmask; @@ -610,7 +600,7 @@ fn check_and_update_one_event_interest<'tcx>( // insert an epoll_return to the ready list. if flags != 0 { let epoll_key = (id, epoll_event_interest.fd_num); - let ready_list = &mut epoll_event_interest.ready_list.mapping.borrow_mut(); + let mut ready_list = epfd.ready_list.mapping.borrow_mut(); let mut event_instance = EpollEventInstance::new(flags, epoll_event_interest.data); // If we are tracking data races, remember the current clock so we can sync with it later. ecx.release_clock(|clock| { @@ -627,23 +617,12 @@ fn check_and_update_one_event_interest<'tcx>( /// Stores the ready list of the `epfd` epoll instance into `events` (which must be an array), /// and the number of returned events into `dest`. fn return_ready_list<'tcx>( - epfd_value: i32, - weak_epfd: WeakFileDescriptionRef, + epfd: &FileDescriptionRef, dest: &MPlaceTy<'tcx>, events: &MPlaceTy<'tcx>, ecx: &mut MiriInterpCx<'tcx>, ) -> InterpResult<'tcx> { - let Some(epfd) = weak_epfd.upgrade() else { - throw_unsup_format!("epoll FD {epfd_value} got closed while blocking.") - }; - - let epoll_file_description = epfd - .downcast::() - .ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_wait`"))?; - - let ready_list = epoll_file_description.get_ready_list(); - - let mut ready_list = ready_list.mapping.borrow_mut(); + let mut ready_list = epfd.ready_list.mapping.borrow_mut(); let mut num_of_events: i32 = 0; let mut array_iter = ecx.project_array_fields(events)?; 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 4bbe417ea8db9..4b76bbb2b4de4 100644 --- a/src/tools/miri/src/shims/unix/linux_like/eventfd.rs +++ b/src/tools/miri/src/shims/unix/linux_like/eventfd.rs @@ -20,7 +20,7 @@ const MAX_COUNTER: u64 = u64::MAX - 1; /// /// #[derive(Debug)] -struct Event { +struct EventFd { /// The object contains an unsigned 64-bit integer (uint64_t) counter that is maintained by the /// kernel. This counter is initialized with the value specified in the argument initval. counter: Cell, @@ -32,13 +32,13 @@ struct Event { blocked_write_tid: RefCell>, } -impl FileDescription for Event { +impl FileDescription for EventFd { fn name(&self) -> &'static str { "event" } fn close<'tcx>( - self: Box, + self, _communicate_allowed: bool, _ecx: &mut MiriInterpCx<'tcx>, ) -> InterpResult<'tcx, io::Result<()>> { @@ -47,8 +47,7 @@ impl FileDescription for Event { /// Read the counter in the buffer and return the counter if succeeded. fn read<'tcx>( - &self, - self_ref: &FileDescriptionRef, + self: FileDescriptionRef, _communicate_allowed: bool, ptr: Pointer, len: usize, @@ -62,11 +61,10 @@ impl FileDescription for Event { return ecx.set_last_error_and_return(ErrorKind::InvalidInput, dest); } - // eventfd read at the size of u64. + // Turn the pointer into a place at the right type. let buf_place = ecx.ptr_to_mplace_unaligned(ptr, ty); - let weak_eventfd = self_ref.downgrade(); - eventfd_read(buf_place, dest, weak_eventfd, ecx) + eventfd_read(buf_place, dest, self, ecx) } /// A write call adds the 8-byte integer value supplied in @@ -82,8 +80,7 @@ impl FileDescription for Event { /// supplied buffer is less than 8 bytes, or if an attempt is /// made to write the value 0xffffffffffffffff. fn write<'tcx>( - &self, - self_ref: &FileDescriptionRef, + self: FileDescriptionRef, _communicate_allowed: bool, ptr: Pointer, len: usize, @@ -97,18 +94,10 @@ impl FileDescription for Event { return ecx.set_last_error_and_return(ErrorKind::InvalidInput, dest); } - // Read the user-supplied value from the pointer. + // Turn the pointer into a place at the right type. let buf_place = ecx.ptr_to_mplace_unaligned(ptr, ty); - let num = ecx.read_scalar(&buf_place)?.to_u64()?; - // u64::MAX as input is invalid because the maximum value of counter is u64::MAX - 1. - if num == u64::MAX { - return ecx.set_last_error_and_return(ErrorKind::InvalidInput, dest); - } - // If the addition does not let the counter to exceed the maximum value, update the counter. - // Else, block. - let weak_eventfd = self_ref.downgrade(); - eventfd_write(num, buf_place, dest, weak_eventfd, ecx) + eventfd_write(buf_place, dest, self, ecx) } fn as_unix(&self) -> &dyn UnixFileDescription { @@ -116,7 +105,7 @@ impl FileDescription for Event { } } -impl UnixFileDescription for Event { +impl UnixFileDescription for EventFd { fn get_epoll_ready_events<'tcx>(&self) -> InterpResult<'tcx, EpollReadyEvents> { // We only check the status of EPOLLIN and EPOLLOUT flags for eventfd. If other event flags // need to be supported in the future, the check should be added here. @@ -178,7 +167,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let fds = &mut this.machine.fds; - let fd_value = fds.insert_new(Event { + let fd_value = fds.insert_new(EventFd { counter: Cell::new(val.into()), is_nonblock, clock: RefCell::new(VClock::default()), @@ -193,19 +182,17 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { /// Block thread if the value addition will exceed u64::MAX -1, /// else just add the user-supplied value to current counter. fn eventfd_write<'tcx>( - num: u64, buf_place: MPlaceTy<'tcx>, dest: &MPlaceTy<'tcx>, - weak_eventfd: WeakFileDescriptionRef, + eventfd: FileDescriptionRef, ecx: &mut MiriInterpCx<'tcx>, ) -> InterpResult<'tcx> { - let Some(eventfd_ref) = weak_eventfd.upgrade() else { - throw_unsup_format!("eventfd FD got closed while blocking.") - }; - - // Since we pass the weak file description ref, it is guaranteed to be - // an eventfd file description. - let eventfd = eventfd_ref.downcast::().unwrap(); + // Figure out which value we should add. + let num = ecx.read_scalar(&buf_place)?.to_u64()?; + // u64::MAX as input is invalid because the maximum value of counter is u64::MAX - 1. + if num == u64::MAX { + return ecx.set_last_error_and_return(ErrorKind::InvalidInput, dest); + } match eventfd.counter.get().checked_add(num) { Some(new_count @ 0..=MAX_COUNTER) => { @@ -217,10 +204,6 @@ fn eventfd_write<'tcx>( // Store new counter value. eventfd.counter.set(new_count); - // The state changed; we check and update the status of all supported event - // types for current file description. - ecx.check_and_update_readiness(&eventfd_ref)?; - // Unblock *all* threads previously blocked on `read`. // We need to take out the blocked thread ids and unblock them together, // because `unblock_threads` may block them again and end up re-adding the @@ -231,6 +214,10 @@ fn eventfd_write<'tcx>( ecx.unblock_thread(thread_id, BlockReason::Eventfd)?; } + // The state changed; we check and update the status of all supported event + // types for current file description. + ecx.check_and_update_readiness(eventfd)?; + // Return how many bytes we consumed from the user-provided buffer. return ecx.write_int(buf_place.layout.size.bytes(), dest); } @@ -244,6 +231,7 @@ fn eventfd_write<'tcx>( eventfd.blocked_write_tid.borrow_mut().push(ecx.active_thread()); + let weak_eventfd = FileDescriptionRef::downgrade(&eventfd); ecx.block_thread( BlockReason::Eventfd, None, @@ -252,11 +240,14 @@ fn eventfd_write<'tcx>( num: u64, buf_place: MPlaceTy<'tcx>, dest: MPlaceTy<'tcx>, - weak_eventfd: WeakFileDescriptionRef, + weak_eventfd: WeakFileDescriptionRef, } - @unblock = |this| { - // When we get unblocked, try again. - eventfd_write(num, buf_place, &dest, weak_eventfd, this) + |this, unblock: UnblockKind| { + assert_eq!(unblock, UnblockKind::Ready); + // When we get unblocked, try again. We know the ref is still valid, + // otherwise there couldn't be a `write` that unblocks us. + let eventfd_ref = weak_eventfd.upgrade().unwrap(); + eventfd_write(buf_place, &dest, eventfd_ref, this) } ), ); @@ -270,17 +261,9 @@ fn eventfd_write<'tcx>( fn eventfd_read<'tcx>( buf_place: MPlaceTy<'tcx>, dest: &MPlaceTy<'tcx>, - weak_eventfd: WeakFileDescriptionRef, + eventfd: FileDescriptionRef, ecx: &mut MiriInterpCx<'tcx>, ) -> InterpResult<'tcx> { - let Some(eventfd_ref) = weak_eventfd.upgrade() else { - throw_unsup_format!("eventfd FD got closed while blocking.") - }; - - // Since we pass the weak file description ref to the callback function, it is guaranteed to be - // an eventfd file description. - let eventfd = eventfd_ref.downcast::().unwrap(); - // Set counter to 0, get old value. let counter = eventfd.counter.replace(0); @@ -293,6 +276,7 @@ fn eventfd_read<'tcx>( eventfd.blocked_read_tid.borrow_mut().push(ecx.active_thread()); + let weak_eventfd = FileDescriptionRef::downgrade(&eventfd); ecx.block_thread( BlockReason::Eventfd, None, @@ -300,11 +284,14 @@ fn eventfd_read<'tcx>( @capture<'tcx> { buf_place: MPlaceTy<'tcx>, dest: MPlaceTy<'tcx>, - weak_eventfd: WeakFileDescriptionRef, + weak_eventfd: WeakFileDescriptionRef, } - @unblock = |this| { - // When we get unblocked, try again. - eventfd_read(buf_place, &dest, weak_eventfd, this) + |this, unblock: UnblockKind| { + assert_eq!(unblock, UnblockKind::Ready); + // When we get unblocked, try again. We know the ref is still valid, + // otherwise there couldn't be a `write` that unblocks us. + let eventfd_ref = weak_eventfd.upgrade().unwrap(); + eventfd_read(buf_place, &dest, eventfd_ref, this) } ), ); @@ -315,10 +302,6 @@ fn eventfd_read<'tcx>( // Return old counter value into user-space buffer. ecx.write_int(counter, &buf_place)?; - // The state changed; we check and update the status of all supported event - // types for current file description. - ecx.check_and_update_readiness(&eventfd_ref)?; - // Unblock *all* threads previously blocked on `write`. // We need to take out the blocked thread ids and unblock them together, // because `unblock_threads` may block them again and end up re-adding the @@ -329,6 +312,10 @@ fn eventfd_read<'tcx>( ecx.unblock_thread(thread_id, BlockReason::Eventfd)?; } + // The state changed; we check and update the status of all supported event + // types for current file description. + ecx.check_and_update_readiness(eventfd)?; + // Tell userspace how many bytes we put into the buffer. return ecx.write_int(buf_place.layout.size.bytes(), dest); } 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 aa291639a6db6..85c963774a159 100644 --- a/src/tools/miri/src/shims/unix/macos/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/macos/foreign_items.rs @@ -3,6 +3,7 @@ use rustc_span::Symbol; use rustc_target::callconv::{Conv, FnAbi}; use super::sync::EvalContextExt as _; +use crate::helpers::check_min_arg_count; use crate::shims::unix::*; use crate::*; @@ -67,6 +68,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let result = this.realpath(path, resolved_path)?; this.write_scalar(result, dest)?; } + "ioctl" => { + // `ioctl` is variadic. The argument count is checked based on the first argument + // in `this.ioctl()`, so we do not use `check_shim` here. + this.check_abi_and_shim_symbol_clash(abi, Conv::C, link_name)?; + let result = this.ioctl(args)?; + this.write_scalar(result, dest)?; + } // Environment related shims "_NSGetEnviron" => { @@ -112,7 +120,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.check_no_isolation("`_NSGetExecutablePath`")?; let buf_ptr = this.read_pointer(buf)?; - let bufsize = this.deref_pointer(bufsize)?; + 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). @@ -234,4 +242,26 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(EmulateItemResult::NeedsReturn) } + + fn ioctl(&mut self, args: &[OpTy<'tcx>]) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + + let fioclex = this.eval_libc_u64("FIOCLEX"); + + let [fd_num, cmd] = check_min_arg_count("ioctl", args)?; + let fd_num = this.read_scalar(fd_num)?.to_i32()?; + let cmd = this.read_scalar(cmd)?.to_u64()?; + + if cmd == fioclex { + // Since we don't support `exec`, this is a NOP. However, we want to + // return EBADF if the FD is invalid. + if this.machine.fds.is_fd_num(fd_num) { + interp_ok(Scalar::from_i32(0)) + } else { + this.set_last_error_and_return_i32(LibcError("EBADF")) + } + } else { + throw_unsup_format!("ioctl: unsupported command {cmd:#x}"); + } + } } diff --git a/src/tools/miri/src/shims/unix/macos/sync.rs b/src/tools/miri/src/shims/unix/macos/sync.rs index f66a57ae7061c..330c64f06a3e6 100644 --- a/src/tools/miri/src/shims/unix/macos/sync.rs +++ b/src/tools/miri/src/shims/unix/macos/sync.rs @@ -64,7 +64,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { None, callback!( @capture<'tcx> {} - @unblock = |_this| { + |_this, _unblock: UnblockKind| { panic!("we shouldn't wake up ever") } ), diff --git a/src/tools/miri/src/shims/unix/solarish/foreign_items.rs b/src/tools/miri/src/shims/unix/solarish/foreign_items.rs index c99e8ae7c6ef0..f94783a390722 100644 --- a/src/tools/miri/src/shims/unix/solarish/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/solarish/foreign_items.rs @@ -3,6 +3,8 @@ use rustc_span::Symbol; use rustc_target::callconv::{Conv, FnAbi}; use crate::shims::unix::foreign_items::EvalContextExt as _; +use crate::shims::unix::linux_like::epoll::EvalContextExt as _; +use crate::shims::unix::linux_like::eventfd::EvalContextExt as _; use crate::shims::unix::*; use crate::*; @@ -21,6 +23,32 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { ) -> InterpResult<'tcx, EmulateItemResult> { let this = self.eval_context_mut(); match link_name.as_str() { + // epoll, eventfd (NOT available on Solaris!) + "epoll_create1" => { + this.assert_target_os("illumos", "epoll_create1"); + let [flag] = this.check_shim(abi, Conv::C, link_name, args)?; + let result = this.epoll_create1(flag)?; + this.write_scalar(result, dest)?; + } + "epoll_ctl" => { + this.assert_target_os("illumos", "epoll_ctl"); + let [epfd, op, fd, event] = this.check_shim(abi, Conv::C, link_name, args)?; + let result = this.epoll_ctl(epfd, op, fd, event)?; + this.write_scalar(result, dest)?; + } + "epoll_wait" => { + this.assert_target_os("illumos", "epoll_wait"); + let [epfd, events, maxevents, timeout] = + this.check_shim(abi, Conv::C, link_name, args)?; + this.epoll_wait(epfd, events, maxevents, timeout, dest)?; + } + "eventfd" => { + this.assert_target_os("illumos", "eventfd"); + let [val, flag] = this.check_shim(abi, Conv::C, link_name, args)?; + let result = this.eventfd(val, flag)?; + this.write_scalar(result, dest)?; + } + // Threading "pthread_setname_np" => { let [thread, name] = this.check_shim(abi, Conv::C, link_name, args)?; @@ -78,6 +106,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(result, dest)?; } + // Sockets and pipes + "__xnet_socketpair" => { + let [domain, type_, protocol, sv] = + this.check_shim(abi, Conv::C, link_name, args)?; + let result = this.socketpair(domain, type_, protocol, sv)?; + this.write_scalar(result, dest)?; + } + // Miscellaneous "___errno" => { let [] = this.check_shim(abi, Conv::C, link_name, args)?; diff --git a/src/tools/miri/src/shims/unix/unnamed_socket.rs b/src/tools/miri/src/shims/unix/unnamed_socket.rs index 86ebe95762a6c..08515b815a900 100644 --- a/src/tools/miri/src/shims/unix/unnamed_socket.rs +++ b/src/tools/miri/src/shims/unix/unnamed_socket.rs @@ -31,7 +31,7 @@ struct AnonSocket { /// The `AnonSocket` file descriptor that is our "peer", and that holds the buffer we are /// writing to. This is a weak reference because the other side may be closed before us; all /// future writes will then trigger EPIPE. - peer_fd: OnceCell, + peer_fd: OnceCell>, /// Indicates whether the peer has lost data when the file description is closed. /// This flag is set to `true` if the peer's `readbuf` is non-empty at the time /// of closure. @@ -58,7 +58,7 @@ impl Buffer { } impl AnonSocket { - fn peer_fd(&self) -> &WeakFileDescriptionRef { + fn peer_fd(&self) -> &WeakFileDescriptionRef { self.peer_fd.get().unwrap() } } @@ -69,7 +69,7 @@ impl FileDescription for AnonSocket { } fn close<'tcx>( - self: Box, + self, _communicate_allowed: bool, ecx: &mut MiriInterpCx<'tcx>, ) -> InterpResult<'tcx, io::Result<()>> { @@ -78,80 +78,35 @@ impl FileDescription for AnonSocket { // notify the peer that data lost has happened in current file description. if let Some(readbuf) = &self.readbuf { if !readbuf.borrow().buf.is_empty() { - peer_fd.downcast::().unwrap().peer_lost_data.set(true); + peer_fd.peer_lost_data.set(true); } } // Notify peer fd that close has happened, since that can unblock reads and writes. - ecx.check_and_update_readiness(&peer_fd)?; + ecx.check_and_update_readiness(peer_fd)?; } interp_ok(Ok(())) } fn read<'tcx>( - &self, - self_ref: &FileDescriptionRef, + self: FileDescriptionRef, _communicate_allowed: bool, ptr: Pointer, len: usize, dest: &MPlaceTy<'tcx>, ecx: &mut MiriInterpCx<'tcx>, ) -> InterpResult<'tcx> { - // Always succeed on read size 0. - if len == 0 { - return ecx.return_read_success(ptr, &[], 0, dest); - } - - let Some(readbuf) = &self.readbuf else { - // FIXME: This should return EBADF, but there's no nice way to do that as there's no - // corresponding ErrorKind variant. - throw_unsup_format!("reading from the write end of a pipe"); - }; - - if readbuf.borrow().buf.is_empty() && self.is_nonblock { - // Non-blocking socketpair with writer and empty buffer. - // https://linux.die.net/man/2/read - // EAGAIN or EWOULDBLOCK can be returned for socket, - // POSIX.1-2001 allows either error to be returned for this case. - // Since there is no ErrorKind for EAGAIN, WouldBlock is used. - return ecx.set_last_error_and_return(ErrorKind::WouldBlock, dest); - } - anonsocket_read(self_ref.downgrade(), len, ptr, dest.clone(), ecx) + anonsocket_read(self, len, ptr, dest, ecx) } fn write<'tcx>( - &self, - self_ref: &FileDescriptionRef, + self: FileDescriptionRef, _communicate_allowed: bool, ptr: Pointer, len: usize, dest: &MPlaceTy<'tcx>, ecx: &mut MiriInterpCx<'tcx>, ) -> InterpResult<'tcx> { - // Always succeed on write size 0. - // ("If count is zero and fd refers to a file other than a regular file, the results are not specified.") - if len == 0 { - return ecx.return_write_success(0, dest); - } - - // We are writing to our peer's readbuf. - let Some(peer_fd) = self.peer_fd().upgrade() else { - // If the upgrade from Weak to Rc fails, it indicates that all read ends have been - // closed. - return ecx.set_last_error_and_return(ErrorKind::BrokenPipe, dest); - }; - - let Some(writebuf) = &peer_fd.downcast::().unwrap().readbuf else { - // FIXME: This should return EBADF, but there's no nice way to do that as there's no - // corresponding ErrorKind variant. - throw_unsup_format!("writing to the reading end of a pipe"); - }; - let available_space = - MAX_SOCKETPAIR_BUFFER_CAPACITY.strict_sub(writebuf.borrow().buf.len()); - if available_space == 0 && self.is_nonblock { - // Non-blocking socketpair with a full buffer. - return ecx.set_last_error_and_return(ErrorKind::WouldBlock, dest); - } - anonsocket_write(self_ref.downgrade(), ptr, len, dest.clone(), ecx) + anonsocket_write(self, ptr, len, dest, ecx) } fn as_unix(&self) -> &dyn UnixFileDescription { @@ -161,50 +116,64 @@ impl FileDescription for AnonSocket { /// Write to AnonSocket based on the space available and return the written byte size. fn anonsocket_write<'tcx>( - weak_self_ref: WeakFileDescriptionRef, + self_ref: FileDescriptionRef, ptr: Pointer, len: usize, - dest: MPlaceTy<'tcx>, + dest: &MPlaceTy<'tcx>, ecx: &mut MiriInterpCx<'tcx>, ) -> InterpResult<'tcx> { - let Some(self_ref) = weak_self_ref.upgrade() else { - // FIXME: We should raise a deadlock error if the self_ref upgrade failed. - throw_unsup_format!("This will be a deadlock error in future") - }; - let self_anonsocket = self_ref.downcast::().unwrap(); - let Some(peer_fd) = self_anonsocket.peer_fd().upgrade() else { + // Always succeed on write size 0. + // ("If count is zero and fd refers to a file other than a regular file, the results are not specified.") + if len == 0 { + return ecx.return_write_success(0, dest); + } + + // We are writing to our peer's readbuf. + let Some(peer_fd) = self_ref.peer_fd().upgrade() else { // If the upgrade from Weak to Rc fails, it indicates that all read ends have been - // closed. - return ecx.set_last_error_and_return(ErrorKind::BrokenPipe, &dest); + // closed. It is an error to write even if there would be space. + return ecx.set_last_error_and_return(ErrorKind::BrokenPipe, dest); }; - let Some(writebuf) = &peer_fd.downcast::().unwrap().readbuf else { - // FIXME: This should return EBADF, but there's no nice way to do that as there's no - // corresponding ErrorKind variant. - throw_unsup_format!("writing to the reading end of a pipe") + + let Some(writebuf) = &peer_fd.readbuf else { + // Writing to the read end of a pipe. + return ecx.set_last_error_and_return(IoError::LibcError("EBADF"), dest); }; + // Let's see if we can write. let available_space = MAX_SOCKETPAIR_BUFFER_CAPACITY.strict_sub(writebuf.borrow().buf.len()); - if available_space == 0 { - // Blocking socketpair with a full buffer. - let dest = dest.clone(); - self_anonsocket.blocked_write_tid.borrow_mut().push(ecx.active_thread()); - ecx.block_thread( - BlockReason::UnnamedSocket, - None, - callback!( - @capture<'tcx> { - weak_self_ref: WeakFileDescriptionRef, - ptr: Pointer, - len: usize, - dest: MPlaceTy<'tcx>, - } - @unblock = |this| { - anonsocket_write(weak_self_ref, ptr, len, dest, this) - } - ), - ); + if self_ref.is_nonblock { + // Non-blocking socketpair with a full buffer. + return ecx.set_last_error_and_return(ErrorKind::WouldBlock, dest); + } else { + self_ref.blocked_write_tid.borrow_mut().push(ecx.active_thread()); + // Blocking socketpair with a full buffer. + // Block the current thread; only keep a weak ref for this. + let weak_self_ref = FileDescriptionRef::downgrade(&self_ref); + let dest = dest.clone(); + ecx.block_thread( + BlockReason::UnnamedSocket, + None, + callback!( + @capture<'tcx> { + weak_self_ref: WeakFileDescriptionRef, + ptr: Pointer, + len: usize, + dest: MPlaceTy<'tcx>, + } + |this, unblock: UnblockKind| { + assert_eq!(unblock, UnblockKind::Ready); + // If we got unblocked, then our peer successfully upgraded its weak + // ref to us. That means we can also upgrade our weak ref. + let self_ref = weak_self_ref.upgrade().unwrap(); + anonsocket_write(self_ref, ptr, len, &dest, this) + } + ), + ); + } } else { + // There is space to write! let mut writebuf = writebuf.borrow_mut(); // Remember this clock so `read` can synchronize with us. ecx.release_clock(|clock| { @@ -218,68 +187,80 @@ fn anonsocket_write<'tcx>( // Need to stop accessing peer_fd so that it can be notified. drop(writebuf); - // Notification should be provided for peer fd as it became readable. - // The kernel does this even if the fd was already readable before, so we follow suit. - ecx.check_and_update_readiness(&peer_fd)?; - let peer_anonsocket = peer_fd.downcast::().unwrap(); // Unblock all threads that are currently blocked on peer_fd's read. - let waiting_threads = std::mem::take(&mut *peer_anonsocket.blocked_read_tid.borrow_mut()); + let waiting_threads = std::mem::take(&mut *peer_fd.blocked_read_tid.borrow_mut()); // FIXME: We can randomize the order of unblocking. for thread_id in waiting_threads { ecx.unblock_thread(thread_id, BlockReason::UnnamedSocket)?; } + // Notification should be provided for peer fd as it became readable. + // The kernel does this even if the fd was already readable before, so we follow suit. + ecx.check_and_update_readiness(peer_fd)?; - return ecx.return_write_success(actual_write_size, &dest); + return ecx.return_write_success(actual_write_size, dest); } interp_ok(()) } /// Read from AnonSocket and return the number of bytes read. fn anonsocket_read<'tcx>( - weak_self_ref: WeakFileDescriptionRef, + self_ref: FileDescriptionRef, len: usize, ptr: Pointer, - dest: MPlaceTy<'tcx>, + dest: &MPlaceTy<'tcx>, ecx: &mut MiriInterpCx<'tcx>, ) -> InterpResult<'tcx> { - let Some(self_ref) = weak_self_ref.upgrade() else { - // FIXME: We should raise a deadlock error if the self_ref upgrade failed. - throw_unsup_format!("This will be a deadlock error in future") - }; - let self_anonsocket = self_ref.downcast::().unwrap(); + // Always succeed on read size 0. + if len == 0 { + return ecx.return_read_success(ptr, &[], 0, dest); + } - let Some(readbuf) = &self_anonsocket.readbuf else { + let Some(readbuf) = &self_ref.readbuf else { // FIXME: This should return EBADF, but there's no nice way to do that as there's no // corresponding ErrorKind variant. throw_unsup_format!("reading from the write end of a pipe") }; if readbuf.borrow_mut().buf.is_empty() { - if self_anonsocket.peer_fd().upgrade().is_none() { + if self_ref.peer_fd().upgrade().is_none() { // Socketpair with no peer and empty buffer. // 0 bytes successfully read indicates end-of-file. - return ecx.return_read_success(ptr, &[], 0, &dest); + return ecx.return_read_success(ptr, &[], 0, dest); + } else if self_ref.is_nonblock { + // Non-blocking socketpair with writer and empty buffer. + // https://linux.die.net/man/2/read + // EAGAIN or EWOULDBLOCK can be returned for socket, + // POSIX.1-2001 allows either error to be returned for this case. + // Since there is no ErrorKind for EAGAIN, WouldBlock is used. + return ecx.set_last_error_and_return(ErrorKind::WouldBlock, dest); } else { + self_ref.blocked_read_tid.borrow_mut().push(ecx.active_thread()); // Blocking socketpair with writer and empty buffer. - let weak_self_ref = weak_self_ref.clone(); - self_anonsocket.blocked_read_tid.borrow_mut().push(ecx.active_thread()); + // Block the current thread; only keep a weak ref for this. + let weak_self_ref = FileDescriptionRef::downgrade(&self_ref); + let dest = dest.clone(); ecx.block_thread( BlockReason::UnnamedSocket, None, callback!( @capture<'tcx> { - weak_self_ref: WeakFileDescriptionRef, + weak_self_ref: WeakFileDescriptionRef, len: usize, ptr: Pointer, dest: MPlaceTy<'tcx>, } - @unblock = |this| { - anonsocket_read(weak_self_ref, len, ptr, dest, this) + |this, unblock: UnblockKind| { + assert_eq!(unblock, UnblockKind::Ready); + // If we got unblocked, then our peer successfully upgraded its weak + // ref to us. That means we can also upgrade our weak ref. + let self_ref = weak_self_ref.upgrade().unwrap(); + anonsocket_read(self_ref, len, ptr, &dest, this) } ), ); } } else { + // There's data to be read! let mut bytes = vec![0; len]; let mut readbuf = readbuf.borrow_mut(); // Synchronize with all previous writes to this buffer. @@ -301,19 +282,18 @@ fn anonsocket_read<'tcx>( // don't know what that *certain number* is, we will provide a notification every time // a read is successful. This might result in our epoll emulation providing more // notifications than the real system. - if let Some(peer_fd) = self_anonsocket.peer_fd().upgrade() { - ecx.check_and_update_readiness(&peer_fd)?; - let peer_anonsocket = peer_fd.downcast::().unwrap(); + if let Some(peer_fd) = self_ref.peer_fd().upgrade() { // Unblock all threads that are currently blocked on peer_fd's write. - let waiting_threads = - std::mem::take(&mut *peer_anonsocket.blocked_write_tid.borrow_mut()); + let waiting_threads = std::mem::take(&mut *peer_fd.blocked_write_tid.borrow_mut()); // FIXME: We can randomize the order of unblocking. for thread_id in waiting_threads { ecx.unblock_thread(thread_id, BlockReason::UnnamedSocket)?; } + // Notify epoll waiters. + ecx.check_and_update_readiness(peer_fd)?; }; - return ecx.return_read_success(ptr, &bytes, actual_read_size, &dest); + return ecx.return_read_success(ptr, &bytes, actual_read_size, dest); } interp_ok(()) } @@ -337,7 +317,7 @@ impl UnixFileDescription for AnonSocket { // Check if is writable. if let Some(peer_fd) = self.peer_fd().upgrade() { - if let Some(writebuf) = &peer_fd.downcast::().unwrap().readbuf { + if let Some(writebuf) = &peer_fd.readbuf { let data_size = writebuf.borrow().buf.len(); let available_space = MAX_SOCKETPAIR_BUFFER_CAPACITY.strict_sub(data_size); if available_space != 0 { @@ -443,8 +423,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { }); // Make the file descriptions point to each other. - fd0.downcast::().unwrap().peer_fd.set(fd1.downgrade()).unwrap(); - fd1.downcast::().unwrap().peer_fd.set(fd0.downgrade()).unwrap(); + fd0.peer_fd.set(FileDescriptionRef::downgrade(&fd1)).unwrap(); + fd1.peer_fd.set(FileDescriptionRef::downgrade(&fd0)).unwrap(); // Insert the file description to the fd table, generating the file descriptors. let sv0 = fds.insert(fd0); @@ -511,8 +491,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { }); // Make the file descriptions point to each other. - fd0.downcast::().unwrap().peer_fd.set(fd1.downgrade()).unwrap(); - fd1.downcast::().unwrap().peer_fd.set(fd0.downgrade()).unwrap(); + fd0.peer_fd.set(FileDescriptionRef::downgrade(&fd1)).unwrap(); + fd1.peer_fd.set(FileDescriptionRef::downgrade(&fd0)).unwrap(); // Insert the file description to the fd table, generating the file descriptors. let pipefd0 = fds.insert(fd0); diff --git a/src/tools/miri/src/shims/windows/handle.rs b/src/tools/miri/src/shims/windows/handle.rs index 3d872b65a6376..c4eb11fbd3f97 100644 --- a/src/tools/miri/src/shims/windows/handle.rs +++ b/src/tools/miri/src/shims/windows/handle.rs @@ -97,7 +97,7 @@ impl Handle { // packs the data into the lower `data_size` bits // and packs the discriminant right above the data - discriminant << data_size | data + (discriminant << data_size) | data } fn new(discriminant: u32, data: u32) -> Option { diff --git a/src/tools/miri/src/shims/windows/sync.rs b/src/tools/miri/src/shims/windows/sync.rs index a394e0430bcd0..4001201bf678a 100644 --- a/src/tools/miri/src/shims/windows/sync.rs +++ b/src/tools/miri/src/shims/windows/sync.rs @@ -111,7 +111,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { pending_place: MPlaceTy<'tcx>, dest: MPlaceTy<'tcx>, } - @unblock = |this| { + |this, unblock: UnblockKind| { + assert_eq!(unblock, UnblockKind::Ready); let ret = this.init_once_try_begin(id, &pending_place, &dest)?; assert!(ret, "we were woken up but init_once_try_begin still failed"); interp_ok(()) diff --git a/src/tools/miri/tests/fail-dep/libc/affinity.rs b/src/tools/miri/tests/fail-dep/libc/affinity.rs index 09f096e46f1e6..3acbd83d0e5f5 100644 --- a/src/tools/miri/tests/fail-dep/libc/affinity.rs +++ b/src/tools/miri/tests/fail-dep/libc/affinity.rs @@ -1,5 +1,4 @@ -//@ignore-target: windows # only very limited libc on Windows -//@ignore-target: apple # `sched_setaffinity` is not supported on macOS +//@only-target: linux # these are Linux-specific APIs //@compile-flags: -Zmiri-disable-isolation -Zmiri-num-cpus=4 fn main() { diff --git a/src/tools/miri/tests/fail-dep/libc/eventfd_block_read_twice.rs b/src/tools/miri/tests/fail-dep/libc/eventfd_block_read_twice.rs index 81a96103db418..0d893663fd6d3 100644 --- a/src/tools/miri/tests/fail-dep/libc/eventfd_block_read_twice.rs +++ b/src/tools/miri/tests/fail-dep/libc/eventfd_block_read_twice.rs @@ -1,4 +1,4 @@ -//@only-target: linux +//@only-target: linux android illumos //~^ERROR: deadlocked //~^^ERROR: deadlocked //@compile-flags: -Zmiri-preemption-rate=0 diff --git a/src/tools/miri/tests/fail-dep/libc/eventfd_block_write_twice.rs b/src/tools/miri/tests/fail-dep/libc/eventfd_block_write_twice.rs index 7a2a6f4eb1a82..9fed47c17d405 100644 --- a/src/tools/miri/tests/fail-dep/libc/eventfd_block_write_twice.rs +++ b/src/tools/miri/tests/fail-dep/libc/eventfd_block_write_twice.rs @@ -1,4 +1,4 @@ -//@only-target: linux +//@only-target: linux android illumos //~^ERROR: deadlocked //~^^ERROR: deadlocked //@compile-flags: -Zmiri-preemption-rate=0 diff --git a/src/tools/miri/tests/fail-dep/libc/libc-epoll-data-race.rs b/src/tools/miri/tests/fail-dep/libc/libc-epoll-data-race.rs index 7bef687e33952..45f6bf6da0968 100644 --- a/src/tools/miri/tests/fail-dep/libc/libc-epoll-data-race.rs +++ b/src/tools/miri/tests/fail-dep/libc/libc-epoll-data-race.rs @@ -2,7 +2,7 @@ //! and we only read one of them, we do not synchronize with the other events //! and therefore still report a data race for things that need to see the second event //! to be considered synchronized. -//@only-target: linux android +//@only-target: linux android illumos // ensure deterministic schedule //@compile-flags: -Zmiri-preemption-rate=0 diff --git a/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs b/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs index 1c6c2f70c1db1..059b24cb8c0be 100644 --- a/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs +++ b/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs @@ -1,7 +1,7 @@ //@compile-flags: -Zmiri-preemption-rate=0 //~^ERROR: deadlocked //~^^ERROR: deadlocked -//@only-target: linux +//@only-target: linux android illumos //@error-in-other-file: deadlock use std::convert::TryInto; diff --git a/src/tools/miri/tests/fail-dep/libc/libc_epoll_unsupported_fd.rs b/src/tools/miri/tests/fail-dep/libc/libc_epoll_unsupported_fd.rs index 03d4b2d66330e..59cf0fc2ba026 100644 --- a/src/tools/miri/tests/fail-dep/libc/libc_epoll_unsupported_fd.rs +++ b/src/tools/miri/tests/fail-dep/libc/libc_epoll_unsupported_fd.rs @@ -1,4 +1,4 @@ -//@only-target: linux +//@only-target: linux android illumos // This is a test for registering unsupported fd with epoll. // Register epoll fd with epoll is allowed in real system, but we do not support this. diff --git a/src/tools/miri/tests/fail-dep/libc/memrchr_null.rs b/src/tools/miri/tests/fail-dep/libc/memrchr_null.rs index 8b34ff4ac2e37..a1d8fd663f88a 100644 --- a/src/tools/miri/tests/fail-dep/libc/memrchr_null.rs +++ b/src/tools/miri/tests/fail-dep/libc/memrchr_null.rs @@ -1,9 +1,8 @@ -//@ignore-target: windows # No `memrchr` on Windows -//@ignore-target: apple # No `memrchr` on some apple targets +//@only-target: linux # `memrchr` is a GNU extension use std::ptr; -// null is explicitly called out as UB in the C docs. +// null is explicitly called out as UB in the C docs for `memchr`. fn main() { unsafe { libc::memrchr(ptr::null(), 0, 0); //~ERROR: null pointer diff --git a/src/tools/miri/tests/fail-dep/libc/socketpair-close-while-blocked.rs b/src/tools/miri/tests/fail-dep/libc/socketpair-close-while-blocked.rs new file mode 100644 index 0000000000000..8413e118819cd --- /dev/null +++ b/src/tools/miri/tests/fail-dep/libc/socketpair-close-while-blocked.rs @@ -0,0 +1,37 @@ +//! This is a regression test for : we had some +//! faulty logic around `release_clock` that led to this code not reporting a data race. +//~^^ERROR: deadlock +//@ignore-target: windows # no libc socketpair on Windows +//@compile-flags: -Zmiri-preemption-rate=0 -Zmiri-address-reuse-rate=0 +//@error-in-other-file: deadlock +use std::thread; + +fn main() { + let mut fds = [-1, -1]; + let res = unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) }; + assert_eq!(res, 0); + + let thread1 = thread::spawn(move || { + let mut buf: [u8; 1] = [0; 1]; + let _res: i32 = unsafe { + libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) //~ERROR: deadlock + .try_into() + .unwrap() + }; + }); + let thread2 = thread::spawn(move || { + // Close the FD that the other thread is blocked on. + unsafe { libc::close(fds[1]) }; + }); + + // Run the other threads. + thread::yield_now(); + + // When they are both done, continue here. + let data = "a".as_bytes().as_ptr(); + let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 1) }; + assert_eq!(res, -1); + + thread1.join().unwrap(); + thread2.join().unwrap(); +} diff --git a/src/tools/miri/tests/fail-dep/libc/socketpair-close-while-blocked.stderr b/src/tools/miri/tests/fail-dep/libc/socketpair-close-while-blocked.stderr new file mode 100644 index 0000000000000..fe196f5d7d79e --- /dev/null +++ b/src/tools/miri/tests/fail-dep/libc/socketpair-close-while-blocked.stderr @@ -0,0 +1,35 @@ +error: deadlock: the evaluated program deadlocked + --> RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC + | +LL | let ret = unsafe { libc::pthread_join(id, ptr::null_mut()) }; + | ^ the evaluated program deadlocked + | + = note: BACKTRACE: + = note: inside `std::sys::pal::PLATFORM::thread::Thread::join` at RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC + = note: inside `std::thread::JoinInner::<'_, ()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC + = note: inside `std::thread::JoinHandle::<()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC +note: inside `main` + --> tests/fail-dep/libc/socketpair-close-while-blocked.rs:LL:CC + | +LL | thread1.join().unwrap(); + | ^^^^^^^^^^^^^^ + +error: deadlock: the evaluated program deadlocked + --> tests/fail-dep/libc/socketpair-close-while-blocked.rs:LL:CC + | +LL | libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) + | ^ the evaluated program deadlocked + | + = note: BACKTRACE on thread `unnamed-ID`: + = note: inside closure at tests/fail-dep/libc/socketpair-close-while-blocked.rs:LL:CC + +error: deadlock: the evaluated program deadlocked + | + = note: the evaluated program deadlocked + = note: (no span available) + = note: BACKTRACE on thread `unnamed-ID`: + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 3 previous errors + diff --git a/src/tools/miri/tests/pass-dep/libc/libc-affinity.rs b/src/tools/miri/tests/pass-dep/libc/libc-affinity.rs index 3c4311efc4cdb..400e3ca3d7db3 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-affinity.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-affinity.rs @@ -1,5 +1,4 @@ -//@ignore-target: windows # only very limited libc on Windows -//@ignore-target: apple # `sched_{g, s}etaffinity` are not supported on macOS +//@only-target: linux # these are Linux-specific APIs //@compile-flags: -Zmiri-disable-isolation -Zmiri-num-cpus=4 #![feature(io_error_more)] #![feature(pointer_is_aligned_to)] diff --git a/src/tools/miri/tests/pass-dep/libc/libc-epoll-blocking.rs b/src/tools/miri/tests/pass-dep/libc/libc-epoll-blocking.rs index e3c42b2701cd3..825e1355848bf 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-epoll-blocking.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-epoll-blocking.rs @@ -1,4 +1,4 @@ -//@only-target: linux android +//@only-target: linux android illumos // test_epoll_block_then_unblock and test_epoll_race depend on a deterministic schedule. //@compile-flags: -Zmiri-preemption-rate=0 diff --git a/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs b/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs index 111e639c86416..23e2122ee50fa 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs @@ -1,4 +1,4 @@ -//@only-target: linux android +//@only-target: linux android illumos use std::convert::TryInto; diff --git a/src/tools/miri/tests/pass-dep/libc/libc-eventfd.rs b/src/tools/miri/tests/pass-dep/libc/libc-eventfd.rs index 2e453215ec910..30e1bbb8fa1b1 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-eventfd.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-eventfd.rs @@ -1,4 +1,4 @@ -//@only-target: linux android +//@only-target: linux android illumos // test_race, test_blocking_read and test_blocking_write depend on a deterministic schedule. //@compile-flags: -Zmiri-preemption-rate=0 @@ -10,7 +10,10 @@ use std::thread; fn main() { test_read_write(); test_race(); + + #[cfg(not(target_os = "illumos"))] test_syscall(); + test_blocking_read(); test_blocking_write(); test_two_threads_blocked_on_eventfd(); @@ -115,6 +118,8 @@ fn test_race() { } // This is a test for calling eventfd2 through a syscall. +// Illumos supports eventfd, but it has no entry to call it through syscall. +#[cfg(not(target_os = "illumos"))] fn test_syscall() { let initval = 0 as libc::c_uint; let flags = (libc::EFD_CLOEXEC | libc::EFD_NONBLOCK) as libc::c_int; 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 f85abe2cc4377..129b18d8e598d 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs @@ -38,6 +38,8 @@ fn main() { test_isatty(); test_read_and_uninit(); test_nofollow_not_symlink(); + #[cfg(target_os = "macos")] + test_ioctl(); } fn test_file_open_unix_allow_two_args() { @@ -431,3 +433,21 @@ fn test_nofollow_not_symlink() { let ret = unsafe { libc::open(cpath.as_ptr(), libc::O_NOFOLLOW | libc::O_CLOEXEC) }; assert!(ret >= 0); } + +#[cfg(target_os = "macos")] +fn test_ioctl() { + let path = utils::prepare_with_content("miri_test_libc_ioctl.txt", &[]); + + let mut name = path.into_os_string(); + name.push("\0"); + let name_ptr = name.as_bytes().as_ptr().cast::(); + unsafe { + // 100 surely is an invalid FD. + assert_eq!(libc::ioctl(100, libc::FIOCLEX), -1); + let errno = std::io::Error::last_os_error().raw_os_error().unwrap(); + assert_eq!(errno, libc::EBADF); + + let fd = libc::open(name_ptr, libc::O_RDONLY); + assert_eq!(libc::ioctl(fd, libc::FIOCLEX), 0); + } +} diff --git a/src/tools/miri/tests/pass-dep/libc/pthread-threadname.rs b/src/tools/miri/tests/pass-dep/libc/pthread-threadname.rs index cf634bc689093..6ac71b5ad1edc 100644 --- a/src/tools/miri/tests/pass-dep/libc/pthread-threadname.rs +++ b/src/tools/miri/tests/pass-dep/libc/pthread-threadname.rs @@ -29,12 +29,13 @@ fn main() { fn set_thread_name(name: &CStr) -> i32 { cfg_if::cfg_if! { - if #[cfg(any(target_os = "linux", target_os = "illumos", target_os = "solaris"))] { + if #[cfg(any( + target_os = "linux", + target_os = "freebsd", + target_os = "illumos", + target_os = "solaris" + ))] { unsafe { libc::pthread_setname_np(libc::pthread_self(), name.as_ptr().cast()) } - } else if #[cfg(target_os = "freebsd")] { - // pthread_set_name_np does not return anything - unsafe { libc::pthread_set_name_np(libc::pthread_self(), name.as_ptr().cast()) }; - 0 } else if #[cfg(target_os = "macos")] { unsafe { libc::pthread_setname_np(name.as_ptr().cast()) } } else { @@ -47,6 +48,7 @@ fn main() { cfg_if::cfg_if! { if #[cfg(any( target_os = "linux", + target_os = "freebsd", target_os = "illumos", target_os = "solaris", target_os = "macos" @@ -54,12 +56,6 @@ fn main() { unsafe { libc::pthread_getname_np(libc::pthread_self(), name.as_mut_ptr().cast(), name.len()) } - } else if #[cfg(target_os = "freebsd")] { - // pthread_get_name_np does not return anything - unsafe { - libc::pthread_get_name_np(libc::pthread_self(), name.as_mut_ptr().cast(), name.len()) - }; - 0 } else { compile_error!("get_thread_name not supported for this OS") } @@ -201,27 +197,25 @@ fn main() { .unwrap(); // Now set the name for a non-existing thread and verify error codes. - // (FreeBSD doesn't return an error code.) - #[cfg(not(target_os = "freebsd"))] - { - let invalid_thread = 0xdeadbeef; - let error = { - cfg_if::cfg_if! { - if #[cfg(target_os = "linux")] { - libc::ENOENT - } else { - libc::ESRCH - } + let invalid_thread = 0xdeadbeef; + let error = { + cfg_if::cfg_if! { + if #[cfg(target_os = "linux")] { + libc::ENOENT + } else { + libc::ESRCH } - }; - #[cfg(not(target_os = "macos"))] - { - // macOS has no `setname` function accepting a thread id as the first argument. - let res = unsafe { libc::pthread_setname_np(invalid_thread, [0].as_ptr()) }; - assert_eq!(res, error); } - let mut buf = [0; 64]; - let res = unsafe { libc::pthread_getname_np(invalid_thread, buf.as_mut_ptr(), buf.len()) }; + }; + + #[cfg(not(target_os = "macos"))] + { + // macOS has no `setname` function accepting a thread id as the first argument. + let res = unsafe { libc::pthread_setname_np(invalid_thread, [0].as_ptr()) }; assert_eq!(res, error); } + + let mut buf = [0; 64]; + let res = unsafe { libc::pthread_getname_np(invalid_thread, buf.as_mut_ptr(), buf.len()) }; + assert_eq!(res, error); } diff --git a/src/tools/miri/tests/pass/shims/pipe.rs b/src/tools/miri/tests/pass/shims/pipe.rs new file mode 100644 index 0000000000000..1be29886d2d32 --- /dev/null +++ b/src/tools/miri/tests/pass/shims/pipe.rs @@ -0,0 +1,13 @@ +//@ignore-target: windows + +#![feature(anonymous_pipe)] + +use std::io::{Read, Write, pipe}; + +fn main() { + let (mut ping_rx, mut ping_tx) = pipe().unwrap(); + ping_tx.write(b"hello").unwrap(); + let mut buf: [u8; 5] = [0; 5]; + ping_rx.read(&mut buf).unwrap(); + assert_eq!(&buf, "hello".as_bytes()); +} diff --git a/src/tools/miri/triagebot.toml b/src/tools/miri/triagebot.toml index 3192882dff6d3..4e013764d8713 100644 --- a/src/tools/miri/triagebot.toml +++ b/src/tools/miri/triagebot.toml @@ -1,3 +1,6 @@ +## See for documentation +## of these options. + [relabel] allow-unauthenticated = [ "A-*", @@ -30,5 +33,10 @@ remove_labels = ["S-waiting-on-author"] # Those labels are added when PR author requests a review from an assignee add_labels = ["S-waiting-on-review"] +[merge-conflicts] +remove = [] +add = ["S-waiting-on-author"] +unless = ["S-blocked", "S-waiting-on-team", "S-waiting-on-review"] + # Automatically close and reopen PRs made by bots to run CI on them [bot-pull-requests]