diff --git a/compiler/rustc_target/src/spec/targets/aarch64_unknown_none.rs b/compiler/rustc_target/src/spec/targets/aarch64_unknown_none.rs index 13d3b77588a0e..297bd0abc864e 100644 --- a/compiler/rustc_target/src/spec/targets/aarch64_unknown_none.rs +++ b/compiler/rustc_target/src/spec/targets/aarch64_unknown_none.rs @@ -30,6 +30,7 @@ pub(crate) fn target() -> Target { stack_probes: StackProbeType::Inline, panic_strategy: PanicStrategy::Abort, default_uwtable: true, + supports_xray: true, ..Default::default() }; Target { diff --git a/compiler/rustc_target/src/spec/targets/aarch64_unknown_none_softfloat.rs b/compiler/rustc_target/src/spec/targets/aarch64_unknown_none_softfloat.rs index 05876891ebd7d..f6527d236d10c 100644 --- a/compiler/rustc_target/src/spec/targets/aarch64_unknown_none_softfloat.rs +++ b/compiler/rustc_target/src/spec/targets/aarch64_unknown_none_softfloat.rs @@ -27,6 +27,7 @@ pub(crate) fn target() -> Target { stack_probes: StackProbeType::Inline, panic_strategy: PanicStrategy::Abort, default_uwtable: true, + supports_xray: true, ..Default::default() }; Target { diff --git a/src/ci/docker/host-aarch64/dist-aarch64-freebsd/Dockerfile b/src/ci/docker/host-aarch64/dist-aarch64-freebsd/Dockerfile new file mode 100644 index 0000000000000..ca597acb72c5c --- /dev/null +++ b/src/ci/docker/host-aarch64/dist-aarch64-freebsd/Dockerfile @@ -0,0 +1,44 @@ +FROM ubuntu:26.04 + +RUN apt-get update && apt-get install -y --no-install-recommends \ + clang \ + make \ + ninja-build \ + file \ + curl \ + ca-certificates \ + python3 \ + git \ + cmake \ + sudo \ + bzip2 \ + xz-utils \ + texinfo \ + wget \ + libssl-dev \ + pkg-config \ + && rm -rf /var/lib/apt/lists/* + +COPY scripts/freebsd-toolchain.sh /tmp/ +RUN /tmp/freebsd-toolchain.sh aarch64 + +COPY scripts/sccache.sh /scripts/ +RUN sh /scripts/sccache.sh + +COPY scripts/cmake.sh /scripts/ +RUN /scripts/cmake.sh + +ENV \ + AR_aarch64_unknown_freebsd=aarch64-unknown-freebsd14-ar \ + CC_aarch64_unknown_freebsd=aarch64-unknown-freebsd14-clang \ + CXX_aarch64_unknown_freebsd=aarch64-unknown-freebsd14-clang++ + +ENV HOSTS=aarch64-unknown-freebsd + +ENV RUST_CONFIGURE_ARGS="--enable-full-tools \ + --enable-extended \ + --enable-profiler \ + --enable-sanitizers \ + --disable-docs" + +ENV SCRIPT="python3 ../x.py dist --host $HOSTS --target $HOSTS" diff --git a/src/ci/docker/scripts/freebsd-toolchain.sh b/src/ci/docker/scripts/freebsd-toolchain.sh index 5308288d911a0..8b4081d27d083 100755 --- a/src/ci/docker/scripts/freebsd-toolchain.sh +++ b/src/ci/docker/scripts/freebsd-toolchain.sh @@ -46,6 +46,7 @@ mkdir -p "$sysroot" case $arch in (x86_64) freebsd_arch=amd64 ;; (i686) freebsd_arch=i386 ;; + (aarch64) freebsd_arch=arm64 ;; esac files_to_extract=( diff --git a/src/ci/github-actions/jobs.yml b/src/ci/github-actions/jobs.yml index 97ddde799a757..448a2c927b394 100644 --- a/src/ci/github-actions/jobs.yml +++ b/src/ci/github-actions/jobs.yml @@ -261,6 +261,9 @@ auto: - name: dist-x86_64-freebsd <<: *job-linux-4c + - name: dist-aarch64-freebsd + <<: *job-aarch64-linux + - name: dist-x86_64-illumos <<: *job-linux-4c diff --git a/src/doc/unstable-book/src/compiler-flags/instrument-xray.md b/src/doc/unstable-book/src/compiler-flags/instrument-xray.md index 7fb33cd68b4a3..68b881df47385 100644 --- a/src/doc/unstable-book/src/compiler-flags/instrument-xray.md +++ b/src/doc/unstable-book/src/compiler-flags/instrument-xray.md @@ -37,3 +37,4 @@ which on their own don't do anything useful. In order to actually trace the functions, you will need to link a separate runtime library of your choice, such as Clang's [XRay Runtime Library](https://www.llvm.org/docs/XRay.html#xray-runtime-library). +On targets where such a runtime is not available but instrumentation is supported, you must supply and link your own runtime library. diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs index 1c162a79c4c44..d081634fafebb 100644 --- a/src/librustdoc/html/highlight.rs +++ b/src/librustdoc/html/highlight.rs @@ -23,8 +23,8 @@ use crate::display::Joined as _; use crate::html::escape::EscapeBodyText; use crate::html::format::HrefInfo; use crate::html::macro_expansion::ExpandedCode; -use crate::html::render::span_map::{DUMMY_SP, Span}; -use crate::html::render::{Context, LinkFromSrc}; +use crate::html::render::Context; +use crate::html::span_map::{DUMMY_SP, LinkFromSrc, Span}; /// This type is needed in case we want to render links on items to allow to go to their definition. pub(crate) struct HrefContext<'a, 'tcx> { diff --git a/src/librustdoc/html/mod.rs b/src/librustdoc/html/mod.rs index d42f4782845d8..382d97265cf69 100644 --- a/src/librustdoc/html/mod.rs +++ b/src/librustdoc/html/mod.rs @@ -8,6 +8,7 @@ pub(crate) mod macro_expansion; pub mod markdown; pub(crate) mod render; pub(crate) mod sources; +pub(crate) mod span_map; pub(crate) mod static_files; pub(crate) mod toc; mod url_parts_builder; diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index a2a43b3b2da75..9e52b03b3210b 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -20,7 +20,7 @@ use tracing::info; use super::print_item::{full_path, print_item, print_item_path, print_ty_path}; use super::sidebar::{ModuleLike, Sidebar, print_sidebar, sidebar_module_like}; -use super::{AllTypes, LinkFromSrc, StylePath, collect_spans_and_sources, scrape_examples_help}; +use super::{AllTypes, StylePath, scrape_examples_help}; use crate::clean::types::ExternalLocation; use crate::clean::utils::has_doc_flag; use crate::clean::{self, ExternalCrate}; @@ -33,8 +33,8 @@ use crate::formats::item_type::ItemType; use crate::html::escape::Escape; use crate::html::macro_expansion::ExpandedCode; use crate::html::markdown::{self, ErrorCodes, IdMap, plain_text_summary}; -use crate::html::render::span_map::Span; use crate::html::render::write_shared::write_shared; +use crate::html::span_map::{LinkFromSrc, Span, collect_spans_and_sources}; use crate::html::url_parts_builder::UrlPartsBuilder; use crate::html::{layout, sources, static_files}; use crate::scrape_examples::AllCallLocations; diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index fe7311719fd54..8108316a856ba 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -36,7 +36,6 @@ mod ordered_json; mod print_item; pub(crate) mod sidebar; mod sorted_template; -pub(crate) mod span_map; mod type_layout; mod write_shared; @@ -64,7 +63,6 @@ use rustc_span::symbol::{Symbol, sym}; use tracing::{debug, info}; pub(crate) use self::context::*; -pub(crate) use self::span_map::{LinkFromSrc, collect_spans_and_sources}; pub(crate) use self::write_shared::*; use crate::clean::{self, Defaultness, Item, ItemId, RenderedLink}; use crate::display::{Joined as _, MaybeDisplay as _}; diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/span_map.rs similarity index 100% rename from src/librustdoc/html/render/span_map.rs rename to src/librustdoc/html/span_map.rs diff --git a/src/tools/miri/miri-script/src/commands.rs b/src/tools/miri/miri-script/src/commands.rs index d469502c243f4..24fe5b66bb0b4 100644 --- a/src/tools/miri/miri-script/src/commands.rs +++ b/src/tools/miri/miri-script/src/commands.rs @@ -114,8 +114,8 @@ impl Command { Command::Check { features, flags } => Self::check(features, flags), Command::Test { bless, target, coverage, features, flags } => Self::test(bless, target, coverage, features, flags), - Command::Run { dep, quiet, target, edition, features, flags } => - Self::run(dep, quiet, target, edition, features, flags), + Command::Run { dep, native, quiet, target, edition, features, flags } => + Self::run(dep, native, quiet, target, edition, features, flags), Command::Doc { features, flags } => Self::doc(features, flags), Command::Fmt { flags } => Self::fmt(flags), Command::Clippy { features, flags } => Self::clippy(features, flags), @@ -465,6 +465,7 @@ impl Command { fn run( dep: bool, + native: bool, quiet: bool, target: Option, edition: Option, @@ -472,8 +473,10 @@ impl Command { flags: Vec, ) -> Result<()> { let mut e = MiriEnv::new()?; + let run_via_ui_test = dep || native; // Preparation: get a sysroot, and get the miri binary. + // We do this even for native run as it also builds Miri itself. let miri_sysroot = e.build_miri_sysroot(/* quiet */ quiet, target.as_deref(), &features)?; let miri_bin = e @@ -484,8 +487,8 @@ impl Command { // (because `flags` may contain `--`). let mut early_flags = Vec::::new(); - // In `dep` mode, the target is already passed via `MIRI_TEST_TARGET` - if !dep { + // In ui_test mode, the target is already passed via `MIRI_TEST_TARGET` + if !run_via_ui_test { if let Some(target) = &target { early_flags.push("--target".into()); early_flags.push(target.into()); @@ -493,35 +496,36 @@ impl Command { } early_flags.push("--edition".into()); early_flags.push(edition.as_deref().unwrap_or("2021").into()); - early_flags.push("--sysroot".into()); - early_flags.push(miri_sysroot.into()); + if !native { + early_flags.push("--sysroot".into()); + early_flags.push(miri_sysroot.into()); + } // Compute flags. let miri_flags = e.sh.var("MIRIFLAGS").unwrap_or_default(); let miri_flags = flagsplit(&miri_flags); - let quiet_flag = if quiet { Some("--quiet") } else { None }; // Run Miri. // The basic command that executes the Miri driver. - let mut cmd = if dep { + let mut cmd = if run_via_ui_test { // We invoke the test suite as that has all the logic for running with dependencies. - e.cargo_cmd(".", "test", &features) + let mut cmd = e + .cargo_cmd(".", "test", &features) .args(&["--test", "ui"]) - .args(quiet_flag) + // This does not show anything useful so we always hide it. + .arg("--quiet") .arg("--") - .args(&["--miri-run-dep-mode"]) + .env("MIRI_RUN_MODE", if native { "native" } else { "1" }); + if let Some(target) = &target { + cmd = cmd.env("MIRI_TEST_TARGET", target); + } + cmd } else { cmd!(e.sh, "{miri_bin}") }; cmd.set_quiet(quiet); // Add Miri flags - let mut cmd = cmd.args(&miri_flags).args(&early_flags).args(&flags); - // For `--dep` we also need to set the target in the env var. - if dep { - if let Some(target) = &target { - cmd = cmd.env("MIRI_TEST_TARGET", target); - } - } + let cmd = cmd.args(&miri_flags).args(&early_flags).args(&flags); // Finally, run the thing. Ok(cmd.run()?) } diff --git a/src/tools/miri/miri-script/src/main.rs b/src/tools/miri/miri-script/src/main.rs index 419c128e5b72f..b575842fc6c78 100644 --- a/src/tools/miri/miri-script/src/main.rs +++ b/src/tools/miri/miri-script/src/main.rs @@ -78,6 +78,10 @@ pub enum Command { /// Build the program with the dependencies declared in `tests/deps/Cargo.toml`. #[arg(long)] dep: bool, + /// Compile and run the program natively instead of via Miri. Implies `--dep`. + /// All flags are passed to rustc; there is currently no way to pass flags to the program. + #[arg(long)] + native: bool, /// Hide build progress. #[arg(long, short)] quiet: bool, diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index 4bb9513424511..5e581cb38e5e6 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -63b1dfc0e00fd6f8ad7cd8817fc712e7d9b7be59 +281c97c3240a9abd984ca0c6a2cd7389115e80d5 diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/diagnostics.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/diagnostics.rs index 16e89ee460de0..9d4c446b14f85 100644 --- a/src/tools/miri/src/borrow_tracker/tree_borrows/diagnostics.rs +++ b/src/tools/miri/src/borrow_tracker/tree_borrows/diagnostics.rs @@ -456,7 +456,7 @@ struct DisplayFmtWrapper { /// will show each permission line as /// ```text /// 0.. 1.. 2.. 3.. 4.. 5 -/// [Act|Res|Frz|Dis|___] +/// [Unq|Res|Frz|Dis|___] /// ``` struct DisplayFmtPermission { /// Text that starts the permission block. @@ -467,7 +467,7 @@ struct DisplayFmtPermission { close: S, /// Text to show when a permission is not initialized. /// Should have the same width as a `Permission`'s `.short_name()`, i.e. - /// 3 if using the `Res/Act/Frz/Dis` notation. + /// 3 if using the `Res/Unq/Frz/Dis` notation. uninit: S, /// Text to separate the `start` and `end` values of a range. range_sep: S, @@ -525,7 +525,7 @@ struct DisplayFmtPadding { /// ``` /// will show states as /// ```text -/// Act +/// Unq /// ?Res /// ____ /// ``` @@ -549,8 +549,8 @@ struct DisplayFmt { } impl DisplayFmt { /// Print the permission with the format - /// ` Res`/` Re*`/` Act`/` Frz`/` Dis` for accessed locations - /// and `?Res`/`?Re*`/`?Act`/`?Frz`/`?Dis` for unaccessed locations. + /// ` Res`/` Re*`/` Unq`/` Frz`/` Dis` for accessed locations + /// and `?Res`/`?Re*`/`?Unq`/`?Frz`/`?Dis` for unaccessed locations. fn print_perm(&self, perm: Option) -> String { if let Some(perm) = perm { format!( @@ -801,7 +801,7 @@ impl DisplayRepr { ) { let mut line = String::new(); // Format the permissions on each range. - // Looks like `| Act| Res| Res| Act|`. + // Looks like `| Unq| Res| Res| Unq|`. line.push_str(fmt.perm.open); for (i, (perm, &pad)) in tree.rperm.iter().zip(padding.iter()).enumerate() { if i > 0 { diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/perms.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/perms.rs index aa15b9e1b1188..0dd4712b85c7d 100644 --- a/src/tools/miri/src/borrow_tracker/tree_borrows/perms.rs +++ b/src/tools/miri/src/borrow_tracker/tree_borrows/perms.rs @@ -462,7 +462,7 @@ pub mod diagnostics { ReservedFrz { conflicted: false } => "Res ", ReservedFrz { conflicted: true } => "ResC", ReservedIM => "ReIM", - Unique => "Act ", + Unique => "Unq ", Frozen => "Frz ", Disabled => "Dis ", } diff --git a/src/tools/miri/src/clock.rs b/src/tools/miri/src/clock.rs index 47608f873a481..6556975095528 100644 --- a/src/tools/miri/src/clock.rs +++ b/src/tools/miri/src/clock.rs @@ -1,5 +1,7 @@ use std::cell::Cell; -use std::time::{Duration, Instant as StdInstant}; +use std::time::{Duration, Instant as StdInstant, SystemTime}; + +use crate::MiriMachine; /// When using a virtual clock, this defines how many nanoseconds we pretend are passing for each /// basic block. @@ -8,6 +10,7 @@ use std::time::{Duration, Instant as StdInstant}; /// (See `tests/pass/shims/time-with-isolation*.rs`.) const NANOSECONDS_PER_BASIC_BLOCK: u128 = 5000; +/// An instant (a fixed moment in time) in Miri's monotone clock. #[derive(Debug)] pub struct Instant { kind: InstantKind, @@ -129,3 +132,79 @@ impl MonotonicClock { } } } + +/// A deadline for some event to occur. +#[derive(Debug)] +pub enum Deadline { + Monotonic(Instant), + RealTime(SystemTime), +} + +impl From for Deadline { + fn from(value: Instant) -> Self { + Deadline::Monotonic(value) + } +} + +impl Deadline { + /// Will try to add `duration`, but if that overflows it may add less. + fn add_lossy(&self, duration: Duration) -> Self { + match self { + Deadline::Monotonic(i) => Deadline::Monotonic(i.add_lossy(duration)), + Deadline::RealTime(s) => { + // If this overflows, try adding just 1h and assume that will not overflow. + Deadline::RealTime( + s.checked_add(duration) + .unwrap_or_else(|| s.checked_add(Duration::from_secs(3600)).unwrap()), + ) + } + } + } +} + +/// The clock to use for the timeout you are asking for. +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum TimeoutClock { + /// The timeout is measured using the monotone clock. + Monotonic, + /// The timeout is measured using the host's system clock. + RealTime, +} + +/// Whether the timeout is relative or absolute. +#[derive(Debug, Copy, Clone)] +pub enum TimeoutStyle { + /// The given duration is interpreted relative to "now" for the selected clock. + Relative, + /// The given duration is interpreted as an "absolute" time, i.e., relative to the epoch of the + /// selected clock. + Absolute, +} + +impl MiriMachine<'_> { + /// Computes the deadline for a given timeout configuration and duration. + pub fn timeout( + &self, + clock: TimeoutClock, + style: TimeoutStyle, + duration: Duration, + ) -> Deadline { + // First let's figure out what "zero" means for the given clock and style. + let zero = match clock { + TimeoutClock::RealTime => { + assert!(self.communicate(), "cannot have `RealTime` timeout with isolation"); + Deadline::RealTime(match style { + TimeoutStyle::Absolute => SystemTime::UNIX_EPOCH, + TimeoutStyle::Relative => SystemTime::now(), + }) + } + TimeoutClock::Monotonic => + Deadline::Monotonic(match style { + TimeoutStyle::Absolute => self.monotonic_clock.epoch(), + TimeoutStyle::Relative => self.monotonic_clock.now(), + }), + }; + // Then add the given duration relative to that "zero". + zero.add_lossy(duration) + } +} diff --git a/src/tools/miri/src/concurrency/blocking_io.rs b/src/tools/miri/src/concurrency/blocking_io.rs index 9dc9bbfd1f1d2..c16aecedb3340 100644 --- a/src/tools/miri/src/concurrency/blocking_io.rs +++ b/src/tools/miri/src/concurrency/blocking_io.rs @@ -355,7 +355,7 @@ pub trait EvalContextExt<'tcx>: MiriInterpCxExt<'tcx> { &mut self, source_fd: FileDescriptionRef, interest: BlockingIoInterest, - timeout: Option<(TimeoutClock, TimeoutAnchor, Duration)>, + deadline: Option, callback: DynUnblockCallback<'tcx>, ) -> InterpResult<'tcx> { let this = self.eval_context_mut(); @@ -370,7 +370,7 @@ pub trait EvalContextExt<'tcx>: MiriInterpCxExt<'tcx> { } else { // The I/O readiness is currently not fulfilled. We block the thread // until the readiness is fulfilled and execute the callback then. - this.block_thread(BlockReason::IO, timeout, callback); + this.block_thread(BlockReason::IO, deadline, callback); interp_ok(()) } } diff --git a/src/tools/miri/src/concurrency/sync.rs b/src/tools/miri/src/concurrency/sync.rs index c529ed5145edd..de2e0d8e23924 100644 --- a/src/tools/miri/src/concurrency/sync.rs +++ b/src/tools/miri/src/concurrency/sync.rs @@ -5,7 +5,6 @@ use std::collections::hash_map::Entry; use std::default::Default; use std::ops::Not; use std::rc::Rc; -use std::time::Duration; use std::{fmt, iter}; use rustc_abi::Size; @@ -684,7 +683,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { &mut self, condvar_ref: CondvarRef, mutex_ref: MutexRef, - timeout: Option<(TimeoutClock, TimeoutAnchor, Duration)>, + deadline: Option, retval_succ: Scalar, retval_timeout: Scalar, dest: MPlaceTy<'tcx>, @@ -706,7 +705,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { condvar_ref.0.borrow_mut().waiters.push_back(thread); this.block_thread( BlockReason::Condvar, - timeout, + deadline, callback!( @capture<'tcx> { condvar_ref: CondvarRef, @@ -763,7 +762,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { &mut self, futex_ref: FutexRef, bitset: u32, - timeout: Option<(TimeoutClock, TimeoutAnchor, Duration)>, + deadline: Option, callback: DynUnblockCallback<'tcx>, ) { let this = self.eval_context_mut(); @@ -776,7 +775,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.block_thread( BlockReason::Futex, - timeout, + deadline, callback!( @capture<'tcx> { futex_ref: FutexRef, diff --git a/src/tools/miri/src/concurrency/thread.rs b/src/tools/miri/src/concurrency/thread.rs index 3d4210276ceeb..2b293a59ddb70 100644 --- a/src/tools/miri/src/concurrency/thread.rs +++ b/src/tools/miri/src/concurrency/thread.rs @@ -125,7 +125,7 @@ enum ThreadState<'tcx> { /// The thread is enabled and can be executed. Enabled, /// The thread is blocked on something. - Blocked { reason: BlockReason, timeout: Option, callback: DynUnblockCallback<'tcx> }, + Blocked { reason: BlockReason, deadline: Option, callback: DynUnblockCallback<'tcx> }, /// The thread has terminated its execution. We do not delete terminated /// threads (FIXME: why?). Terminated, @@ -135,8 +135,11 @@ impl<'tcx> std::fmt::Debug for ThreadState<'tcx> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Enabled => write!(f, "Enabled"), - Self::Blocked { reason, timeout, .. } => - f.debug_struct("Blocked").field("reason", reason).field("timeout", timeout).finish(), + Self::Blocked { reason, deadline, .. } => + f.debug_struct("Blocked") + .field("reason", reason) + .field("deadline", deadline) + .finish(), Self::Terminated => write!(f, "Terminated"), } } @@ -375,52 +378,6 @@ impl VisitProvenance for Frame<'_, Provenance, FrameExtra<'_>> { } } -/// The moment in time when a blocked thread should be woken up. -#[derive(Debug)] -enum Timeout { - Monotonic(Instant), - RealTime(SystemTime), -} - -impl Timeout { - /// How long do we have to wait from now until the specified time? - fn get_wait_time(&self, clock: &MonotonicClock) -> Duration { - match self { - Timeout::Monotonic(instant) => instant.duration_since(clock.now()), - Timeout::RealTime(time) => - time.duration_since(SystemTime::now()).unwrap_or(Duration::ZERO), - } - } - - /// Will try to add `duration`, but if that overflows it may add less. - fn add_lossy(&self, duration: Duration) -> Self { - match self { - Timeout::Monotonic(i) => Timeout::Monotonic(i.add_lossy(duration)), - Timeout::RealTime(s) => { - // If this overflows, try adding just 1h and assume that will not overflow. - Timeout::RealTime( - s.checked_add(duration) - .unwrap_or_else(|| s.checked_add(Duration::from_secs(3600)).unwrap()), - ) - } - } - } -} - -/// The clock to use for the timeout you are asking for. -#[derive(Debug, Copy, Clone, PartialEq)] -pub enum TimeoutClock { - Monotonic, - RealTime, -} - -/// Whether the timeout is relative or absolute. -#[derive(Debug, Copy, Clone)] -pub enum TimeoutAnchor { - Relative, - Absolute, -} - /// An error signaling that the requested thread doesn't exist or has terminated. #[derive(Debug, Copy, Clone)] pub enum ThreadLookupError { @@ -655,12 +612,12 @@ impl<'tcx> ThreadManager<'tcx> { fn block_thread( &mut self, reason: BlockReason, - timeout: Option, + deadline: Option, callback: DynUnblockCallback<'tcx>, ) { let state = &mut self.threads[self.active_thread].state; assert!(state.is_enabled()); - *state = ThreadState::Blocked { reason, timeout, callback } + *state = ThreadState::Blocked { reason, deadline, callback } } /// Change the active thread to some enabled thread. @@ -755,7 +712,7 @@ trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> { // `pthread_cond_timedwait`, "an error is returned if [...] the absolute time specified by // abstime has already been passed at the time of the call". // - let potential_sleep_time = this.unblock_expired_timeouts()?; + let potential_sleep_time = this.unblock_expired_deadlines()?; let thread_manager = &mut this.machine.threads; let rng = this.machine.rng.get_mut(); @@ -856,19 +813,27 @@ trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> { /// Find all threads with expired timeouts, unblock them and execute their timeout callbacks. /// - /// This method returns the minimum duration until the next thread timeout expires. - /// If all ready threads have no timeout set, [`None`] is returned. - fn unblock_expired_timeouts(&mut self) -> InterpResult<'tcx, Option> { + /// This method returns the minimum duration until the next thread deadline. + /// If all ready threads have no deadline set, [`None`] is returned. + fn unblock_expired_deadlines(&mut self) -> InterpResult<'tcx, Option> { let this = self.eval_context_mut(); - let clock = &this.machine.monotonic_clock; + let communicate = this.machine.communicate(); let mut min_wait_time = Option::::None; let mut callbacks = Vec::new(); for (id, thread) in this.machine.threads.threads.iter_enumerated_mut() { match &thread.state { - ThreadState::Blocked { timeout: Some(timeout), .. } => { - let wait_time = timeout.get_wait_time(clock); + ThreadState::Blocked { deadline: Some(deadline), .. } => { + let wait_time = match deadline { + Deadline::Monotonic(instant) => + instant.duration_since(this.machine.monotonic_clock.now()), + Deadline::RealTime(time) => { + assert!(communicate, "cannot have `RealTime` timeout with isolation"); + time.duration_since(SystemTime::now()).unwrap_or(Duration::ZERO) + } + }; + if wait_time.is_zero() { // The timeout expired for this thread. let old_state = mem::replace(&mut thread.state, ThreadState::Enabled); @@ -1098,34 +1063,17 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { fn block_thread( &mut self, reason: BlockReason, - timeout: Option<(TimeoutClock, TimeoutAnchor, Duration)>, + deadline: Option, callback: DynUnblockCallback<'tcx>, ) { let this = self.eval_context_mut(); - if timeout.is_some() && this.machine.data_race.as_genmc_ref().is_some() { + if deadline.is_some() && this.machine.data_race.as_genmc_ref().is_some() { panic!("Unimplemented: Timeouts not yet supported in GenMC mode."); } - let timeout = timeout.map(|(clock, anchor, duration)| { - let anchor = match clock { - TimeoutClock::RealTime => { - assert!( - this.machine.communicate(), - "cannot have `RealTime` timeout with isolation enabled!" - ); - Timeout::RealTime(match anchor { - TimeoutAnchor::Absolute => SystemTime::UNIX_EPOCH, - TimeoutAnchor::Relative => SystemTime::now(), - }) - } - TimeoutClock::Monotonic => - Timeout::Monotonic(match anchor { - TimeoutAnchor::Absolute => this.machine.monotonic_clock.epoch(), - TimeoutAnchor::Relative => this.machine.monotonic_clock.now(), - }), - }; - anchor.add_lossy(duration) - }); - this.machine.threads.block_thread(reason, timeout, callback); + if matches!(deadline, Some(Deadline::RealTime(_))) && !this.machine.communicate() { + panic!("cannot have `RealTime` timeout with isolation"); + } + this.machine.threads.block_thread(reason, deadline, callback); } /// Put the blocked thread into the enabled state. diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs index 944ff933ac9ba..160f31d50c4bf 100644 --- a/src/tools/miri/src/lib.rs +++ b/src/tools/miri/src/lib.rs @@ -133,7 +133,7 @@ pub use crate::borrow_tracker::tree_borrows::{EvalContextExt as _, Tree}; pub use crate::borrow_tracker::{ BorTag, BorrowTrackerMethod, EvalContextExt as _, TreeBorrowsParams, }; -pub use crate::clock::{Instant, MonotonicClock}; +pub use crate::clock::{Deadline, Instant, MonotonicClock, TimeoutClock, TimeoutStyle}; pub use crate::concurrency::blocking_io::{ BlockingIoInterest, BlockingIoManager, BlockingIoSourceReadiness, EvalContextExt as _, SourceFileDescription, @@ -146,7 +146,7 @@ pub use crate::concurrency::init_once::{EvalContextExt as _, InitOnceRef}; pub use crate::concurrency::sync::{CondvarRef, EvalContextExt as _, MutexRef, RwLockRef}; pub use crate::concurrency::thread::{ BlockReason, DynUnblockCallback, EvalContextExt as _, StackEmptyCallback, ThreadId, - ThreadManager, TimeoutAnchor, TimeoutClock, UnblockKind, + ThreadManager, UnblockKind, }; pub use crate::concurrency::{GenmcConfig, GenmcCtx, run_genmc_mode}; pub use crate::data_structures::dedup_range_map::DedupRangeMap; diff --git a/src/tools/miri/src/provenance_gc.rs b/src/tools/miri/src/provenance_gc.rs index 02353411eb944..3656a9eaa87c2 100644 --- a/src/tools/miri/src/provenance_gc.rs +++ b/src/tools/miri/src/provenance_gc.rs @@ -44,6 +44,12 @@ where } } +impl VisitProvenance for Vec { + fn visit_provenance(&self, visit: &mut VisitWith<'_>) { + self.iter().for_each(|el| el.visit_provenance(visit)); + } +} + impl VisitProvenance for std::cell::RefCell { fn visit_provenance(&self, visit: &mut VisitWith<'_>) { self.borrow().visit_provenance(visit) diff --git a/src/tools/miri/src/shims/native_lib/mod.rs b/src/tools/miri/src/shims/native_lib/mod.rs index c66082583b3e4..27571a9eb7c87 100644 --- a/src/tools/miri/src/shims/native_lib/mod.rs +++ b/src/tools/miri/src/shims/native_lib/mod.rs @@ -381,7 +381,9 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { let mut fields = vec![]; for field in &adt_def.non_enum_variant().fields { - let layout = this.layout_of(field.ty(*this.tcx, args).skip_norm_wip()).map_err(|_err| orig_ty)?; + let layout = this + .layout_of(field.ty(*this.tcx, args).skip_norm_wip()) + .map_err(|_err| orig_ty)?; fields.push(this.ty_to_ffitype(layout)?); } diff --git a/src/tools/miri/src/shims/time.rs b/src/tools/miri/src/shims/time.rs index 66bf50e5f18d2..87dded0766ad8 100644 --- a/src/tools/miri/src/shims/time.rs +++ b/src/tools/miri/src/shims/time.rs @@ -334,11 +334,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let deadline = this.read_scalar(deadline_op)?.to_u64()?; // Our mach_absolute_time "ticks" are plain nanoseconds. - let duration = Duration::from_nanos(deadline); + let deadline = Duration::from_nanos(deadline); + // This is *absolute* time. + let deadline = this.machine.monotonic_clock.epoch().add_lossy(deadline); this.block_thread( BlockReason::Sleep, - Some((TimeoutClock::Monotonic, TimeoutAnchor::Absolute, duration)), + Some(deadline.into()), callback!( @capture<'tcx> {} |_this, unblock: UnblockKind| { @@ -362,10 +364,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let Some(duration) = this.read_timespec(&duration)? else { return this.set_last_error_and_return_i32(LibcError("EINVAL")); }; + let deadline = this.machine.monotonic_clock.now().add_lossy(duration); this.block_thread( BlockReason::Sleep, - Some((TimeoutClock::Monotonic, TimeoutAnchor::Relative, duration)), + Some(deadline.into()), callback!( @capture<'tcx> {} |_this, unblock: UnblockKind| { @@ -401,14 +404,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return this.set_last_error_and_return_i32(LibcError("EINVAL")); }; - let timeout_anchor = if flags == 0 { + let timeout_style = if flags == 0 { // No flags set, the timespec should be interpreted as a duration - // to sleep for - TimeoutAnchor::Relative + // to sleep for, i.e., a relative time. + TimeoutStyle::Relative } else if flags == this.eval_libc_i32("TIMER_ABSTIME") { // Only flag TIMER_ABSTIME set, the timespec should be interpreted as // an absolute time. - TimeoutAnchor::Absolute + TimeoutStyle::Absolute } else { // The standard lib (through `sleep_until`) only needs TIMER_ABSTIME throw_unsup_format!( @@ -416,10 +419,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { TIMER_ABSTIME is supported" ); }; + let deadline = this.machine.timeout(TimeoutClock::Monotonic, timeout_style, duration); this.block_thread( BlockReason::Sleep, - Some((TimeoutClock::Monotonic, timeout_anchor, duration)), + Some(deadline), callback!( @capture<'tcx> {} |_this, unblock: UnblockKind| { @@ -440,10 +444,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let timeout_ms = this.read_scalar(timeout)?.to_u32()?; let duration = Duration::from_millis(timeout_ms.into()); + let deadline = this.machine.monotonic_clock.now().add_lossy(duration); this.block_thread( BlockReason::Sleep, - Some((TimeoutClock::Monotonic, TimeoutAnchor::Relative, duration)), + Some(deadline.into()), callback!( @capture<'tcx> {} |_this, unblock: UnblockKind| { diff --git a/src/tools/miri/src/shims/unix/fd.rs b/src/tools/miri/src/shims/unix/fd.rs index 4a1dc10341646..ab17be0e04f4e 100644 --- a/src/tools/miri/src/shims/unix/fd.rs +++ b/src/tools/miri/src/shims/unix/fd.rs @@ -5,10 +5,10 @@ use std::io; use std::io::ErrorKind; use rand::RngExt; -use rustc_abi::Size; +use rustc_abi::{Align, Size}; use rustc_target::spec::Os; -use crate::shims::files::FileDescription; +use crate::shims::files::{DynFileDescriptionRef, FileDescription}; use crate::shims::sig::check_min_vararg_count; use crate::shims::unix::linux_like::epoll::EpollReadiness; use crate::shims::unix::*; @@ -316,7 +316,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { .min(u64::try_from(this.target_isize_max()).unwrap()) .min(u64::try_from(isize::MAX).unwrap()); let count = usize::try_from(count).unwrap(); // now it fits in a `usize` - let communicate = this.machine.communicate(); // Get the FD. let Some(fd) = this.machine.fds.get(fd_num) else { @@ -324,33 +323,17 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return this.set_last_error_and_return(LibcError("EBADF"), dest); }; - // Handle the zero-sized case. The man page says: - // > If count is zero, read() may detect the errors described below. In the absence of any - // > errors, or if read() does not check for errors, a read() with a count of 0 returns zero - // > and has no other effects. - if count == 0 { - this.write_null(dest)?; - return interp_ok(()); - } - // Non-deterministically decide to further reduce the count, simulating a partial read (but - // never to 0, that would indicate EOF). - let count = if this.machine.short_fd_operations - && fd.short_fd_operations() - && count >= 2 - && this.machine.rng.get_mut().random() - { - count / 2 // since `count` is at least 2, the result is still at least 1 - } else { - count - }; - trace!("read: FD mapped to {fd:?}"); // We want to read at most `count` bytes. We are sure that `count` is not negative // because it was a target's `usize`. Also we are sure that it's smaller than // `usize::MAX` because it is bounded by the host's `isize`. - let finish = { - let dest = dest.clone(); + let dest = dest.clone(); + this.read_from_fd( + fd, + buf, + count, + offset, callback!( @capture<'tcx> { count: usize, @@ -363,22 +346,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // This must fit since `count` fits. this.write_int(u64::try_from(read_size).unwrap(), &dest) } - Err(e) => { - this.set_last_error_and_return(e, &dest) - } + Err(e) => this.set_last_error_and_return(e, &dest) }} - ) - }; - match offset { - None => fd.read(communicate, buf, count, this, finish)?, - Some(offset) => { - let Ok(offset) = u64::try_from(offset) else { - return this.set_last_error_and_return(LibcError("EINVAL"), dest); - }; - fd.as_unix(this).pread(communicate, offset, buf, count, this, finish)? - } - }; - interp_ok(()) + ), + ) } fn write( @@ -402,64 +373,379 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { .min(u64::try_from(this.target_isize_max()).unwrap()) .min(u64::try_from(isize::MAX).unwrap()); let count = usize::try_from(count).unwrap(); // now it fits in a `usize` - let communicate = this.machine.communicate(); // We temporarily dup the FD to be able to retain mutable access to `this`. let Some(fd) = this.machine.fds.get(fd_num) else { return this.set_last_error_and_return(LibcError("EBADF"), dest); }; + let dest = dest.clone(); + this.write_to_fd( + fd, + buf, + count, + offset, + callback!( + @capture<'tcx> { + count: usize, + dest: MPlaceTy<'tcx>, + } + |this, result: Result| { + match result { + Ok(write_size) => { + assert!(write_size <= count); + // This must fit since `count` fits. + this.write_int(u64::try_from(write_size).unwrap(), &dest) + } + Err(e) => this.set_last_error_and_return(e, &dest) + + }} + ), + ) + } + + /// Vectored reads are implemented by first reading bytes from `fd` + /// into a temporary buffer which has the combined size of all buffers in + /// `iov`. After that we split the bytes of the combined buffer into the + /// buffers of `iov`. This ensures that the vectored read occurs atomically. + fn readv( + &mut self, + fd: &OpTy<'tcx>, + iov: &OpTy<'tcx>, + iovcnt: &OpTy<'tcx>, + offset: Option<&OpTy<'tcx>>, + dest: &MPlaceTy<'tcx>, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + + let fd = this.read_scalar(fd)?.to_i32()?; + let iov_ptr = this.read_pointer(iov)?; + let iovcnt: u64 = this.read_scalar(iovcnt)?.to_i32()?.try_into().unwrap(); + // `readv` is the same as `preadv` without an offset. + let offset = if let Some(offset) = offset { + if matches!(this.tcx.sess.target.os, Os::Solaris) { + throw_unsup_format!( + "preadv: vectored reads with offsets aren't supported on Solaris" + ) + } + Some(this.read_scalar(offset)?.to_int(offset.layout.size)?) + } else { + None + }; + + // Check that the FD exists. + let Some(fd) = this.machine.fds.get(fd) else { + return this.set_last_error_and_return(LibcError("EBADF"), dest); + }; + + let iovec_layout = this.libc_array_ty_layout("iovec", iovcnt); + let iov_ptr_mplace = this.ptr_to_mplace(iov_ptr, iovec_layout); + + // Read list of buffers from `iov`. + let mut buffers = Vec::new(); + + let mut array = this.project_array_fields(&iov_ptr_mplace)?; + while let Some((_idx, iovec)) = array.next(this)? { + let iov_len_field = this.project_field_named(&iovec, "iov_len")?; + let iov_len: u64 = this + .read_scalar(&iov_len_field)? + .to_int(iov_len_field.layout.size)? + .try_into() + .unwrap(); + + let iov_base_field = this.project_field_named(&iovec, "iov_base")?; + let iov_base_ptr = this.read_pointer(&iov_base_field)?; + + buffers.push((iov_base_ptr, iov_len)); + } + + let total_bytes = buffers.iter().map(|(_, len)| len).sum::(); + + // Allocate a temporary buffer which has the combined size of all buffers provided in `iov`. + let tmp_ptr: Pointer = this + .allocate_ptr( + Size::from_bytes(total_bytes), + Align::ONE, + MemoryKind::Stack, + AllocInit::Uninit, + )? + .into(); + + let dest = dest.clone(); + this.read_from_fd( + fd, + tmp_ptr, + usize::try_from(total_bytes).unwrap(), + offset, + callback!( + @capture<'tcx> { + tmp_ptr: Pointer, + buffers: Vec<(Pointer, u64)>, + dest: MPlaceTy<'tcx> + } |this, result: Result| { + let bytes_read = match result { + Ok(size) => { + this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest)?; + u64::try_from(size).unwrap() + }, + Err(e) => return this.set_last_error_and_return(e, &dest) + }; + let mut remaining_bytes = bytes_read; + + // Split the bytes from the temporary buffer into the buffers provided in `iov`. + // We start at the first buffer and fill them in order, until we reach the end of the + // initialized bytes in the temporary buffer. + for (buffer_ptr, buffer_len) in buffers { + // Offset temporary buffer by the amount of bytes we already copied into previous buffers. + let tmp_ptr_with_offset = + this.ptr_offset_inbounds(tmp_ptr, i64::try_from(bytes_read.strict_sub(remaining_bytes)).unwrap())?; + + // Copy at most as many bytes as the buffer fits but without reading + // any uninitialized bytes from the temporary buffer. + let copy_amount = buffer_len.min(remaining_bytes); + this.mem_copy( + tmp_ptr_with_offset, + buffer_ptr, + Size::from_bytes(copy_amount), + // The buffers are guaranteed to not overlap because we just newly allocated + // the `tmp_ptr`, and `tmp_ptr_with_offset` is guaranteed to be + // within those boundaries. + true, + )?; + + remaining_bytes = remaining_bytes.strict_sub(copy_amount); + if remaining_bytes == 0 { + // We don't have anything left to copy; exit the loop. + break; + } + } + + this.deallocate_ptr(tmp_ptr, None, MemoryKind::Stack) + }), + ) + } + + /// Vectored writes are implemented by first writing the bytes from all + /// buffers of `iov` into a combined temporary buffer and then writing this + /// combined buffer into `fd`. This ensures that the vectored write occurs atomically. + fn writev( + &mut self, + fd: &OpTy<'tcx>, + iov: &OpTy<'tcx>, + iovcnt: &OpTy<'tcx>, + offset: Option<&OpTy<'tcx>>, + dest: &MPlaceTy<'tcx>, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + + let fd = this.read_scalar(fd)?.to_i32()?; + let iov_ptr = this.read_pointer(iov)?; + let iovcnt: u64 = this.read_scalar(iovcnt)?.to_i32()?.try_into().unwrap(); + // `writev` is the same as `pwritev` without an offset. + let offset = if let Some(offset) = offset { + if matches!(this.tcx.sess.target.os, Os::Solaris) { + throw_unsup_format!( + "pwritev: vectored writes with offsets aren't supported on Solaris" + ) + } + Some(this.read_scalar(offset)?.to_int(offset.layout.size)?) + } else { + None + }; + + // Check that the FD exists. + let Some(fd) = this.machine.fds.get(fd) else { + return this.set_last_error_and_return(LibcError("EBADF"), dest); + }; + + let iovec_layout = this.libc_array_ty_layout("iovec", iovcnt); + let iov_ptr_mplace = this.ptr_to_mplace(iov_ptr, iovec_layout); + + // Read list of buffers from `iov`. + let mut buffers = Vec::new(); + + let mut array = this.project_array_fields(&iov_ptr_mplace)?; + while let Some((_idx, iovec)) = array.next(this)? { + let iov_len_field = this.project_field_named(&iovec, "iov_len")?; + let iov_len: u64 = this + .read_scalar(&iov_len_field)? + .to_int(iov_len_field.layout.size)? + .try_into() + .unwrap(); + + let iov_base_field = this.project_field_named(&iovec, "iov_base")?; + let iov_base_ptr = this.read_pointer(&iov_base_field)?; + + buffers.push((iov_base_ptr, iov_len)); + } + + let total_bytes = buffers.iter().map(|(_, len)| len).sum::(); + + // Allocate a temporary buffer which has the combined size of all buffers provided in `iov`. + let tmp_ptr: Pointer = this + .allocate_ptr( + Size::from_bytes(total_bytes), + Align::ONE, + MemoryKind::Stack, + AllocInit::Uninit, + )? + .into(); + + // Copy the bytes from all buffers provided in `iov` into the temporary buffer. + // We start at the first buffer and then continue buffer by buffer. + let mut bytes_copied: u64 = 0; + for (buffer_ptr, buffer_len) in buffers { + // Offset temporary buffer by the amount of bytes we already copied from previous buffers. + let tmp_ptr_with_offset = + this.ptr_offset_inbounds(tmp_ptr, i64::try_from(bytes_copied).unwrap())?; + + this.mem_copy( + buffer_ptr, + tmp_ptr_with_offset, + Size::from_bytes(buffer_len), + // The buffers are guaranteed to not overlap because we just newly allocated + // the `tmp_ptr`, and `tmp_ptr_with_offset` is guaranteed to be + // within those boundaries. + true, + )?; + + bytes_copied = bytes_copied.strict_add(buffer_len); + } + + let dest = dest.clone(); + // Write bytes from the temporary buffer. This ensures the write is atomic. + this.write_to_fd( + fd, + tmp_ptr, + usize::try_from(total_bytes).unwrap(), + offset, + callback!( + @capture<'tcx> { + tmp_ptr: Pointer, + dest: MPlaceTy<'tcx>, + } + |this, result: Result| { + this.deallocate_ptr(tmp_ptr, None, MemoryKind::Stack)?; + match result { + Ok(size) => this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest), + Err(e) => this.set_last_error_and_return(e, &dest) + } + }), + ) + } +} + +impl<'tcx> EvalContextPrivExt<'tcx> for crate::MiriInterpCx<'tcx> {} +trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { + /// Read `len` bytes from the `fd` file description at `offset` into the buffer + /// pointed to by `ptr`. + /// If `offset` is [`Some`], the read occurs at the given absolute position rather + /// than the current file position (`read_at` semantics rather than `read`). + /// `finish` will be invoked when the read is done (which might be way after + /// this function returns as the read may block). + fn read_from_fd( + &mut self, + fd: DynFileDescriptionRef, + ptr: Pointer, + len: usize, + offset: Option, + finish: DynMachineCallback<'tcx, Result>, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + + // Handle the zero-sized case. The man page says: + // > If count is zero, read() may detect the errors described below. In the absence of any + // > errors, or if read() does not check for errors, a read() with a count of 0 returns zero + // > and has no other effects. + if len == 0 { + return finish.call(this, Ok(0)); + } + + // Non-deterministically decide to further reduce the length, simulating a partial read (but + // never to 0, that would indicate EOF). + let len = if this.machine.short_fd_operations + && fd.short_fd_operations() + && len >= 2 + && this.machine.rng.get_mut().random() + { + len / 2 // since `len` is at least 2, the result is still at least 1 + } else { + len + }; + + match offset { + None => fd.read(this.machine.communicate(), ptr, len, this, finish)?, + Some(offset) => { + let Ok(offset) = u64::try_from(offset) else { + return finish.call(this, Err(LibcError("EINVAL"))); + }; + fd.as_unix(this).pread( + this.machine.communicate(), + offset, + ptr, + len, + this, + finish, + )? + } + }; + interp_ok(()) + } + + /// Write `len` bytes at `offset` from the buffer pointed to by `ptr` into the `fd` + /// file description. + /// If `offset` is [`Some`], the write occurs at the given absolute position rather + /// than the current file position (`write_at` semantics rather than `write`). + /// `finish` will be invoked when the write is done (which might be way after + /// this function returns as the write may block). + fn write_to_fd( + &mut self, + fd: DynFileDescriptionRef, + ptr: Pointer, + len: usize, + offset: Option, + finish: DynMachineCallback<'tcx, Result>, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + // Handle the zero-sized case. The man page says: // > If count is zero and fd refers to a regular file, then write() may return a failure // > status if one of the errors below is detected. If no errors are detected, or error // > detection is not performed, 0 is returned without causing any other effect. If count // > is zero and fd refers to a file other than a regular file, the results are not // > specified. - if count == 0 { + if len == 0 { // For now let's not open the can of worms of what exactly "not specified" could mean... - this.write_null(dest)?; - return interp_ok(()); + return finish.call(this, Ok(0)); } - // Non-deterministically decide to further reduce the count, simulating a partial write. + + // Non-deterministically decide to further reduce the length, simulating a partial write. // We avoid reducing the write size to 0: the docs seem to be entirely fine with that, // but the standard library is not (https://github.com/rust-lang/rust/issues/145959). - let count = if this.machine.short_fd_operations + let len = if this.machine.short_fd_operations && fd.short_fd_operations() - && count >= 2 + && len >= 2 && this.machine.rng.get_mut().random() { - count / 2 + len / 2 } else { - count + len }; - let finish = { - let dest = dest.clone(); - callback!( - @capture<'tcx> { - count: usize, - dest: MPlaceTy<'tcx>, - } - |this, result: Result| { - match result { - Ok(write_size) => { - assert!(write_size <= count); - // This must fit since `count` fits. - this.write_int(u64::try_from(write_size).unwrap(), &dest) - } - Err(e) => { - this.set_last_error_and_return(e, &dest) - } - }} - ) - }; match offset { - None => fd.write(communicate, buf, count, this, finish)?, + None => fd.write(this.machine.communicate(), ptr, len, this, finish)?, Some(offset) => { let Ok(offset) = u64::try_from(offset) else { - return this.set_last_error_and_return(LibcError("EINVAL"), dest); + return finish.call(this, Err(LibcError("EINVAL"))); }; - fd.as_unix(this).pwrite(communicate, buf, count, offset, this, finish)? + fd.as_unix(this).pwrite( + this.machine.communicate(), + ptr, + len, + offset, + this, + finish, + )? } }; interp_ok(()) diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs index 2ed708975c7d5..67602a4fe88e2 100644 --- a/src/tools/miri/src/shims/unix/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/foreign_items.rs @@ -226,8 +226,25 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { trace!("Called write({:?}, {:?}, {:?})", fd, buf, count); this.write(fd, buf, count, None, dest)?; } + "readv" => { + let [fd, iov, iovcnt] = this.check_shim_sig( + shim_sig!(extern "C" fn(i32, *const _, i32) -> isize), + link_name, + abi, + args, + )?; + this.readv(fd, iov, iovcnt, None, dest)?; + } + "writev" => { + let [fd, iov, iovcnt] = this.check_shim_sig( + shim_sig!(extern "C" fn(i32, *const _, i32) -> isize), + link_name, + abi, + args, + )?; + this.writev(fd, iov, iovcnt, None, dest)?; + } "pread" => { - // FIXME: This does not have a direct test (#3179). let [fd, buf, count, offset] = this.check_shim_sig( shim_sig!(extern "C" fn(i32, *mut _, usize, libc::off_t) -> isize), link_name, @@ -241,7 +258,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.read(fd, buf, count, Some(offset), dest)?; } "pwrite" => { - // FIXME: This does not have a direct test (#3179). let [fd, buf, n, offset] = this.check_shim_sig( shim_sig!(extern "C" fn(i32, *const _, usize, libc::off_t) -> isize), link_name, @@ -255,6 +271,25 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { trace!("Called pwrite({:?}, {:?}, {:?}, {:?})", fd, buf, count, offset); this.write(fd, buf, count, Some(offset), dest)?; } + "preadv" => { + let [fd, iov, iovcnt, offset] = this.check_shim_sig( + shim_sig!(extern "C" fn(i32, *const _, i32, libc::off_t) -> isize), + link_name, + abi, + args, + )?; + this.readv(fd, iov, iovcnt, Some(offset), dest)?; + } + "pwritev" => { + let [fd, iov, iovcnt, offset] = this.check_shim_sig( + shim_sig!(extern "C" fn(i32, *const _, i32, libc::off_t) -> isize), + link_name, + abi, + args, + )?; + this.writev(fd, iov, iovcnt, Some(offset), dest)?; + } + "close" => { let [fd] = this.check_shim_sig( shim_sig!(extern "C" fn(i32) -> i32), diff --git a/src/tools/miri/src/shims/unix/freebsd/sync.rs b/src/tools/miri/src/shims/unix/freebsd/sync.rs index 7c46dd549bc0a..32338391f2dd8 100644 --- a/src/tools/miri/src/shims/unix/freebsd/sync.rs +++ b/src/tools/miri/src/shims/unix/freebsd/sync.rs @@ -93,7 +93,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // extended variant, `struct _umtx_time`, as the `uaddr2` argument of _umtx_op(). // They are distinguished by the `uaddr` value, which must be equal // to the size of the structure pointed to by `uaddr2`, casted to uintptr_t. - let timeout = if this.ptr_is_null(uaddr2)? { + let deadline = if this.ptr_is_null(uaddr2)? { // no timeout parameter None } else { @@ -105,13 +105,17 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return this.set_last_error_and_return(LibcError("EINVAL"), dest); }; - let anchor = if umtx_time.abs_time { - TimeoutAnchor::Absolute + let style = if umtx_time.abs_time { + TimeoutStyle::Absolute } else { - TimeoutAnchor::Relative + TimeoutStyle::Relative }; - Some((umtx_time.timeout_clock, anchor, umtx_time.timeout)) + Some(this.machine.timeout( + umtx_time.timeout_clock, + style, + umtx_time.timeout, + )) } else if uaddr == timespec_layout.size.bytes() { // RealTime clock can't be used in isolation mode. this.check_no_isolation("`_umtx_op` with `timespec` timeout")?; @@ -127,7 +131,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // code (umtx_copyin_umtx_time() in kern_umtx.c), it seems to default to CLOCK_REALTIME, // so that's what we also do. // Discussion in golang: https://github.com/golang/go/issues/17168#issuecomment-250235271 - Some((TimeoutClock::RealTime, TimeoutAnchor::Relative, duration)) + Some(this.machine.timeout( + TimeoutClock::RealTime, + TimeoutStyle::Relative, + duration, + )) } else { return this.set_last_error_and_return(LibcError("EINVAL"), dest); } @@ -137,7 +145,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.futex_wait( futex_ref, u32::MAX, // we set the bitset to include all bits - timeout, + deadline, callback!( @capture<'tcx> { dest: MPlaceTy<'tcx>, diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index 0caae04d93644..a5b3f891d9ca7 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -4,7 +4,7 @@ use std::borrow::Cow; use std::ffi::OsString; use std::fs::{self, DirBuilder, File, FileType, OpenOptions, TryLockError}; use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write}; -use std::path::{self, Path, PathBuf}; +use std::path::{self, Path}; use std::time::SystemTime; use rustc_abi::Size; @@ -750,7 +750,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // However, `statx` is allowed to return information that was not requested or to not // return information that was requested. This `mask` represents the information we can // actually provide for any target. - let mut mask = this.eval_libc_u32("STATX_TYPE") | this.eval_libc_u32("STATX_SIZE"); + let mut mask = this.eval_libc_u32("STATX_TYPE") + | this.eval_libc_u32("STATX_MODE") + | this.eval_libc_u32("STATX_SIZE"); // Check which pieces of metadata we acquired, and set the appropriate flags in the mask. if metadata.ino.is_some() { @@ -1650,13 +1652,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Write the modified template back to the passed in pointer to maintain POSIX semantics. this.write_bytes_ptr(template_ptr, template_bytes.iter().copied())?; - // To actually open the file, turn this into a host OsString. - let p = bytes_to_os_str(template_bytes)?.to_os_string(); - - let possibly_unique = std::env::temp_dir().join::(p.into()); - - let file = fopts.open(possibly_unique); - + // See if we can create and open this file. + let file = fopts.open(bytes_to_os_str(template_bytes)?); match file { Ok(f) => { let fd = this.machine.fds.insert_new(FileHandle { file: f, writable: true }); 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 1758c04c8981e..d2d978bffee7d 100644 --- a/src/tools/miri/src/shims/unix/linux_like/epoll.rs +++ b/src/tools/miri/src/shims/unix/linux_like/epoll.rs @@ -473,11 +473,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // If the timeout is 0 or there is a ready event, we can return immediately. return_ready_list(&epfd, dest, &event, this)?; } else { - // Blocking - let timeout = match timeout { + // Blocking, with a relative timeout. + let deadline = match timeout { 0.. => { let duration = Duration::from_millis(timeout.try_into().unwrap()); - Some((TimeoutClock::Monotonic, TimeoutAnchor::Relative, duration)) + Some(this.machine.monotonic_clock.now().add_lossy(duration).into()) } -1 => None, ..-1 => { @@ -495,7 +495,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // a thread is permanently blocked so this is fine. this.block_thread( BlockReason::Epoll { epfd: epfd.clone() }, - timeout, + deadline, callback!( @capture<'tcx> { epfd: FileDescriptionRef, 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 76374ca24d0da..ee318a265fd30 100644 --- a/src/tools/miri/src/shims/unix/linux_like/eventfd.rs +++ b/src/tools/miri/src/shims/unix/linux_like/eventfd.rs @@ -97,8 +97,9 @@ impl FileDescription for EventFd { ) -> InterpResult<'tcx> { // We're treating the buffer as a `u64`. let ty = ecx.machine.layouts.u64; - // Check the size of slice, and return error only if the size of the slice < 8. - if len < ty.layout.size.bytes_usize() { + // Check the size of slice, and return error if the size is wrong. The docs say we only + // error when the size is too small, but Linux seems to also error when the size is too big. + if len != ty.layout.size.bytes_usize() { return finish.call(ecx, Err(ErrorKind::InvalidInput.into())); } diff --git a/src/tools/miri/src/shims/unix/linux_like/sync.rs b/src/tools/miri/src/shims/unix/linux_like/sync.rs index 00df4ee2b2ae3..5a1a9ed535941 100644 --- a/src/tools/miri/src/shims/unix/linux_like/sync.rs +++ b/src/tools/miri/src/shims/unix/linux_like/sync.rs @@ -68,7 +68,7 @@ pub fn futex<'tcx>( } let timeout = ecx.deref_pointer_as(timeout, ecx.libc_ty_layout("timespec"))?; - let timeout = if ecx.ptr_is_null(timeout.ptr())? { + let deadline = if ecx.ptr_is_null(timeout.ptr())? { None } else { let Some(duration) = ecx.read_timespec(&timeout)? else { @@ -82,14 +82,14 @@ pub fn futex<'tcx>( } else { TimeoutClock::Monotonic }; - let timeout_anchor = if wait_bitset { + let timeout_style = if wait_bitset { // FUTEX_WAIT_BITSET uses an absolute timestamp. - TimeoutAnchor::Absolute + TimeoutStyle::Absolute } else { // FUTEX_WAIT uses a relative timestamp. - TimeoutAnchor::Relative + TimeoutStyle::Relative }; - Some((timeout_clock, timeout_anchor, duration)) + Some(ecx.machine.timeout(timeout_clock, timeout_style, duration)) }; // There may be a concurrent thread changing the value of addr // and then invoking the FUTEX_WAKE syscall. It is critical that the @@ -154,7 +154,7 @@ pub fn futex<'tcx>( ecx.futex_wait( futex_ref, bitset, - timeout, + deadline, callback!( @capture<'tcx> { dest: MPlaceTy<'tcx>, diff --git a/src/tools/miri/src/shims/unix/macos/sync.rs b/src/tools/miri/src/shims/unix/macos/sync.rs index 6ee9ffaf37762..ea0fd5c5b0a3c 100644 --- a/src/tools/miri/src/shims/unix/macos/sync.rs +++ b/src/tools/miri/src/shims/unix/macos/sync.rs @@ -145,12 +145,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { MacOsFutexTimeout::Relative { clock_op, timeout_op } => { let clock = this.read_scalar(clock_op)?.to_u32()?; let timeout = this.read_scalar(timeout_op)?.to_u64()?; - Some((clock, TimeoutAnchor::Relative, timeout)) + Some((clock, TimeoutStyle::Relative, timeout)) } MacOsFutexTimeout::Absolute { clock_op, timeout_op } => { let clock = this.read_scalar(clock_op)?.to_u32()?; let timeout = this.read_scalar(timeout_op)?.to_u64()?; - Some((clock, TimeoutAnchor::Absolute, timeout)) + Some((clock, TimeoutStyle::Absolute, timeout)) } }; @@ -168,12 +168,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } let is_shared = flags == shared; - let timeout = clock_timeout.map(|(_, anchor, timeout)| { - // The only clock that is currently supported is the monotonic clock. + let deadline = clock_timeout.map(|(_, style, timeout)| { + // The only clock that is currently supported is the monotonic clock (checked above). // While the deadline argument of `os_sync_wait_on_address_with_deadline` // is actually not in nanoseconds but in the units of `mach_current_time`, // the two are equivalent in miri. - (TimeoutClock::Monotonic, anchor, Duration::from_nanos(timeout)) + this.machine.timeout(TimeoutClock::Monotonic, style, Duration::from_nanos(timeout)) }); // See the Linux futex implementation for why this fence exists. @@ -213,7 +213,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.futex_wait( futex_ref.clone(), u32::MAX, // bitset - timeout, + deadline, callback!( @capture<'tcx> { dest: MPlaceTy<'tcx>, diff --git a/src/tools/miri/src/shims/unix/socket.rs b/src/tools/miri/src/shims/unix/socket.rs index 50904effa9fde..2e9a26974eff5 100644 --- a/src/tools/miri/src/shims/unix/socket.rs +++ b/src/tools/miri/src/shims/unix/socket.rs @@ -3,7 +3,6 @@ use std::io; use std::io::Read; use std::net::{Ipv4Addr, Shutdown, SocketAddr, SocketAddrV4}; use std::sync::atomic::AtomicBool; -use std::time::Duration; use mio::event::Source; use mio::net::{TcpListener, TcpStream}; @@ -46,6 +45,13 @@ enum SocketState { /// For a socket created using the `connect` syscall, this is /// only reachable from the [`SocketState::Connecting`] state. Connected(TcpStream), + /// The SO_ERROR socket option has been set after calling + /// the `connect` syscall, indicating that the connection + /// attempt failed. By the POSIX specification, a socket is + /// is an unspecified state after a failed connection attempt + /// and thus nothing (except destroying the socket) should be + /// supported when a socket is in this state. + ConnectionFailed(TcpStream), } #[derive(Debug)] @@ -78,7 +84,10 @@ impl FileDescription for Socket { if matches!( &*self.state.borrow(), - SocketState::Listening(_) | SocketState::Connecting(_) | SocketState::Connected(_) + SocketState::Listening(_) + | SocketState::Connecting(_) + | SocketState::Connected(_) + | SocketState::ConnectionFailed(_) ) { // There exists an associated host socket so we need to deregister it // from the blocking I/O manager. @@ -374,6 +383,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { }; assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!"); + this.ensure_not_failed(&socket, "bind")?; let mut state = socket.state.borrow_mut(); @@ -412,6 +422,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { "bind: socket is already bound and binding a socket \ multiple times is unsupported" ), + SocketState::ConnectionFailed(_) => unreachable!(), } interp_ok(Scalar::from_i32(0)) @@ -435,6 +446,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { }; assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!"); + this.ensure_not_failed(&socket, "listen")?; let mut state = socket.state.borrow_mut(); @@ -461,6 +473,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { SocketState::Connecting(_) | SocketState::Connected(_) => { throw_unsup_format!("listen: listening on a connected socket is unsupported") } + SocketState::ConnectionFailed(_) => unreachable!(), } interp_ok(Scalar::from_i32(0)) @@ -496,6 +509,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { }; assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!"); + this.ensure_not_failed(&socket, "accept4")?; if !matches!(*socket.state.borrow(), SocketState::Listening(_)) { throw_unsup_format!( @@ -590,6 +604,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { }; assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!"); + this.ensure_not_failed(&socket, "connect")?; match &*socket.state.borrow() { SocketState::Initial => { /* fall-through to below */ } @@ -633,15 +648,20 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let dest = dest.clone(); this.ensure_connected( - socket, + socket.clone(), /* should_wait */ true, "connect", callback!( @capture<'tcx> { + socket: FileDescriptionRef, dest: MPlaceTy<'tcx> } |this, result: Result<(), ()>| { if result.is_err() { - this.set_last_error_and_return(LibcError("ENOTCONN"), &dest) + // An error occurred whilst connecting. We know + // that it has been consumed by `ensure_connected` + // and is now stored in `socket.error`. + let err = socket.error.take().unwrap(); + this.set_last_error_and_return(err, &dest) } else { this.write_scalar(Scalar::from_i32(0), &dest) } @@ -895,6 +915,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let socket = this.read_scalar(socket)?.to_i32()?; let level = this.read_scalar(level)?.to_i32()?; let option_name = this.read_scalar(option_name)?.to_i32()?; + let option_value_ptr = this.read_pointer(option_value)?; let socklen_layout = this.libc_ty_layout("socklen_t"); let option_len = this.read_scalar(option_len)?.to_int(socklen_layout.size)?; @@ -903,7 +924,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return this.set_last_error_and_return_i32(LibcError("EBADF")); }; - let Some(_socket) = fd.downcast::() else { + let Some(socket) = fd.downcast::() else { // Man page specifies to return ENOTSOCK if `fd` is not a socket. return this.set_last_error_and_return_i32(LibcError("ENOTSOCK")); }; @@ -921,7 +942,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return this.set_last_error_and_return_i32(LibcError("EINVAL")); } let option_value = - this.deref_pointer_as(option_value, this.machine.layouts.i32)?; + this.ptr_to_mplace(option_value_ptr, this.machine.layouts.i32); let _val = this.read_scalar(&option_value)?.to_i32()?; // We entirely ignore this value since we do not support signals anyway. @@ -934,7 +955,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Option value should be C-int which is usually 4 bytes. return this.set_last_error_and_return_i32(LibcError("EINVAL")); } - let option_value = this.deref_pointer_as(option_value, this.machine.layouts.i32)?; + let option_value = this.ptr_to_mplace(option_value_ptr, this.machine.layouts.i32); let _val = this.read_scalar(&option_value)?.to_i32()?; // We entirely ignore this: std always sets REUSEADDR for us, and in the end it's more of a // hint to bypass some arbitrary timeout anyway. @@ -944,10 +965,74 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { "setsockopt: option {option_name:#x} is unsupported for level SOL_SOCKET", ); } + } else if level == this.eval_libc_i32("IPPROTO_IP") { + let opt_ip_ttl = this.eval_libc_i32("IP_TTL"); + + if option_name == opt_ip_ttl { + if option_len != 4 { + // Option value should be C-uint which is usually 4 bytes. + return this.set_last_error_and_return_i32(LibcError("EINVAL")); + } + let option_value = this.ptr_to_mplace(option_value_ptr, this.machine.layouts.u32); + let ttl = this.read_scalar(&option_value)?.to_u32()?; + + let result = match &*socket.state.borrow() { + SocketState::Initial | SocketState::Bound(_) => + throw_unsup_format!( + "setsockopt: setting option IP_TTL on level IPPROTO_IP is only supported \ + on connected and listening sockets" + ), + SocketState::Listening(listener) => listener.set_ttl(ttl), + SocketState::Connecting(stream) | SocketState::Connected(stream) => + stream.set_ttl(ttl), + SocketState::ConnectionFailed(_) => unreachable!(), + }; + + return match result { + Ok(_) => interp_ok(Scalar::from_i32(0)), + Err(e) => this.set_last_error_and_return_i32(e), + }; + } else { + throw_unsup_format!( + "setsockopt: option {option_name:#x} is unsupported for level IPPROTO_IP", + ); + } + } else if level == this.eval_libc_i32("IPPROTO_TCP") { + let opt_tcp_nodelay = this.eval_libc_i32("TCP_NODELAY"); + + if option_name == opt_tcp_nodelay { + if option_len != 4 { + // Option value should be C-int which is usually 4 bytes. + return this.set_last_error_and_return_i32(LibcError("EINVAL")); + } + let option_value = this.ptr_to_mplace(option_value_ptr, this.machine.layouts.i32); + let nodelay = this.read_scalar(&option_value)?.to_i32()? != 0; + + let result = match &*socket.state.borrow() { + SocketState::Initial | SocketState::Bound(_) | SocketState::Listening(_) => + throw_unsup_format!( + "setsockopt: setting option TCP_NODELAY on level IPPROTO_TCP is only supported \ + on connected sockets" + ), + SocketState::Connecting(stream) | SocketState::Connected(stream) => + stream.set_nodelay(nodelay), + SocketState::ConnectionFailed(_) => unreachable!(), + }; + + return match result { + Ok(_) => interp_ok(Scalar::from_i32(0)), + Err(e) => this.set_last_error_and_return_i32(e), + }; + } else { + throw_unsup_format!( + "setsockopt: option {option_name:#x} is unsupported for level IPPROTO_TCP" + ); + } } throw_unsup_format!( - "setsockopt: level {level:#x} is unsupported, only SOL_SOCKET is allowed" + "setsockopt: level {level:#x} is unsupported, only SOL_SOCKET, IPPROTO_IP \ + and IPPROTO_TCP are allowed" ); } @@ -1002,17 +1087,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let opt_so_error = this.eval_libc_i32("SO_ERROR"); if option_name == opt_so_error { - // Because `TcpStream::take_error()` and `TcpListener::take_error()` consume the latest async - // error, we know that our stored `socket.error` is outdated when `TcpStream::take_error()`/ - // `TcpListener::take_error()` returns `Ok(Some(...))`. - // If they return `Ok(None)`, then we fall back to the stored `socket.error`. - let error = match &*socket.state.borrow() { - SocketState::Initial | SocketState::Bound(_) => socket.error.take(), - SocketState::Listening(listener) => - listener.take_error().unwrap_or(socket.error.take()), - SocketState::Connecting(stream) | SocketState::Connected(stream) => - stream.take_error().unwrap_or(socket.error.take()), + // Reading SO_ERROR should always return the latest async error. Because our stored + // `socket.error` could be outdated, we attempt to update it here. + this.update_last_error(&socket); + + let return_value = match socket.error.take() { + Some(err) => this.io_error_to_errnum(err)?.to_i32()?, + // If there is no error, we return 0 as the option value. + None => 0, }; + // Clear our own stored error -- it was either `take`n above or it is outdated. socket.error.replace(None); @@ -1021,12 +1105,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { socket.io_readiness.borrow_mut().error = false; this.update_epoll_active_events(socket, /* force_edge */ false)?; - let return_value = match error { - Some(err) => this.io_error_to_errnum(err)?.to_i32()?, - // If there is no error, we write 0 into the option value buffer. - None => 0, - }; - // Allocate new buffer on the stack with the `i32` layout. let value_buffer = this.allocate(this.machine.layouts.i32, MemoryKind::Stack)?; this.write_int(return_value, &value_buffer)?; @@ -1049,6 +1127,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { SocketState::Listening(listener) => listener.ttl(), SocketState::Connecting(stream) | SocketState::Connected(stream) => stream.ttl(), + SocketState::ConnectionFailed(_) => unreachable!(), }; let ttl = match ttl { @@ -1065,9 +1144,39 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { "getsockopt: option {option_name:#x} is unsupported for level IPPROTO_IP", ); } + } else if level == this.eval_libc_i32("IPPROTO_TCP") { + let opt_tcp_nodelay = this.eval_libc_i32("TCP_NODELAY"); + + if option_name == opt_tcp_nodelay { + let nodelay = match &*socket.state.borrow() { + SocketState::Initial | SocketState::Bound(_) | SocketState::Listening(_) => + throw_unsup_format!( + "getsockopt: reading option TCP_NODELAY on level IPPROTO_TCP is only supported \ + on connected sockets" + ), + SocketState::Connecting(stream) | SocketState::Connected(stream) => + stream.nodelay(), + SocketState::ConnectionFailed(_) => unreachable!(), + }; + + let nodelay = match nodelay { + Ok(nodelay) => nodelay, + Err(e) => return this.set_last_error_and_return_i32(e), + }; + + // Allocate new buffer on the stack with the `i32` layout. + let value_buffer = this.allocate(this.machine.layouts.i32, MemoryKind::Stack)?; + this.write_int(i32::from(nodelay), &value_buffer)?; + value_buffer + } else { + throw_unsup_format!( + "getsockopt: option {option_name:#x} is unsupported for level IPPROTO_TCP" + ); + } } else { throw_unsup_format!( - "getsockopt: level {level:#x} is unsupported, only SOL_SOCKET is allowed" + "getsockopt: level {level:#x} is unsupported, only SOL_SOCKET, IPPROTO_IP \ + and IPPROTO_TCP are allowed" ) }; @@ -1120,6 +1229,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { }; assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!"); + this.ensure_not_failed(&socket, "getsockname")?; let state = socket.state.borrow(); @@ -1163,6 +1273,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // For non-bound sockets the POSIX manual says the returned address is unspecified. // Often this is 0.0.0.0:0 and thus we set it to this value. SocketState::Initial => SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0)), + SocketState::ConnectionFailed(_) => unreachable!(), }; this.write_socket_address(&address, address_ptr, address_len_ptr, "getsockname") @@ -1252,6 +1363,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { }; assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!"); + this.ensure_not_failed(&socket, "shutdown")?; let state = socket.state.borrow(); @@ -1605,12 +1717,12 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // would be returned on UNIX-like systems. We thus remap this error to an EWOULDBLOCK. interp_ok(Err(IoError::HostError(io::ErrorKind::WouldBlock.into()))) } - Ok(bytes_read) if bytes_read < length && bytes_read > 0 => { - // We had a short read. (Note that reading 0 bytes is guaranteed to indicate EOF, - // and can never happen spuriously, so we have to exclude that case.) On Unix hosts - // using the `epoll` and `kqueue` backends, a short read means that the read buffer - // is empty. We update the readiness accordingly, which means that next time we see - // "readable" we will report an epoll edge. Some applications (e.g. tokio) rely on + Ok(bytes_read) if !should_peek && bytes_read < length && bytes_read > 0 => { + // We had a short read (and were not peeking). (Note that reading 0 bytes is guaranteed + // to indicate EOF, and can never happen spuriously, so we have to exclude that case.) + // On Unix hosts using the `epoll` and `kqueue` backends, a short read means that the + // read buffer is empty. We update the readiness accordingly, which means that next time + // we see "readable" we will report an epoll edge. Some applications (e.g. tokio) rely on // this behavior; see // if cfg!(any( @@ -1653,10 +1765,13 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Execute the provided callback function when the socket is either in // [`SocketState::Connected`] or an error occurred. /// If the socket is currently neither in the [`SocketState::Connecting`] nor - /// the [`SocketState::Connecting`] state, an ENOTCONN error is returned. - /// When the callback function is called with `Ok(_)`, then we're guaranteed + /// the [`SocketState::Connecting`] state, [`Err`] is returned. + /// When the callback function is called with [`Ok`], then we're guaranteed /// that the socket is in the [`SocketState::Connected`] state. /// + /// This method internally calls `ensure_not_failed` and thus an unsupported + /// error is thrown should `socket` be in [`SocketState::ConnectionFailed`]. + /// /// This function can optionally also block until either an error occurred or /// the socket reached the [`SocketState::Connected`] state. fn ensure_connected( @@ -1677,6 +1792,7 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } _ => { drop(state); + this.ensure_not_failed(&socket, foreign_name)?; return action.call(this, Err(())); } }; @@ -1688,16 +1804,13 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // If we should wait until the connection is established, the timeout is `None`. // Otherwise, we use a zero duration timeout, i.e. we return immediately // (but we still go through the scheduler once -- which is fine). - let timeout = if should_wait { - None - } else { - Some((TimeoutClock::Monotonic, TimeoutAnchor::Absolute, Duration::ZERO)) - }; + let deadline = + if should_wait { None } else { Some(this.machine.monotonic_clock.now().into()) }; this.block_thread_for_io( socket.clone(), BlockingIoInterest::Write, - timeout, + deadline, callback!( @capture<'tcx> { socket: FileDescriptionRef, @@ -1717,9 +1830,9 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // The thread woke up because it's ready, indicating a writeable or error event. - let mut state = socket.state.borrow_mut(); - let stream = match &*state { - SocketState::Connecting(stream) => stream, + let state = socket.state.borrow(); + match &*state { + SocketState::Connecting(_) => { /* fall-through to below */ }, SocketState::Connected(_) => { drop(state); // This can happen because we blocked the thread: @@ -1732,25 +1845,19 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Since this thread just got rescheduled, it could be that another // thread realized that the connection failed and we're thus in // an "invalid state". + this.ensure_not_failed(&socket, foreign_name)?; return action.call(this, Err(())) } }; - // Manually check whether there were any errors since calling `connect`. - if let Ok(Some(err)) = stream.take_error() { - // There was an error during connecting and thus we - // return ENOTCONN. It's the program's responsibility - // to read SO_ERROR itself. + drop(state); - // Store the error such that we can return it when - // `getsockopt(SOL_SOCKET, SO_ERROR, ...)` is called on the socket. - socket.error.replace(Some(err)); + // Set `socket.error` if `socket` currently has an error. + this.update_last_error(&socket); - // Go back to initial state since the only way of getting into the - // `Connecting` state is from the `Initial` state and at this point - // we know that the connection won't be established anymore. - *state = SocketState::Initial; - drop(state); + if socket.error.borrow().is_some() { + // There was an error during connecting. + // It's the program's responsibility to read SO_ERROR itself. return action.call(this, Err(())) } @@ -1772,6 +1879,7 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // The connection is established. // Temporarily use dummy state to take ownership of the stream. + let mut state = socket.state.borrow_mut(); let SocketState::Connecting(stream) = std::mem::replace(&mut*state, SocketState::Initial) else { // At the start of the function we ensured that we're currently connecting. unreachable!() @@ -1783,6 +1891,64 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { ), ) } + + /// Ensure that `socket` is not in the [`SocketState::ConnectionFailed`] state. + /// If `socket` is currently in [`SocketState::ConnectionFailed`], an unsupported + /// error is thrown. + fn ensure_not_failed( + &self, + socket: &FileDescriptionRef, + foreign_name: &'static str, + ) -> InterpResult<'tcx> { + if let SocketState::ConnectionFailed(_) = &*socket.state.borrow() { + throw_unsup_format!( + "{foreign_name}: sockets are in an unspecified state after a failed `connect`; \ + any operation on such a socket is thus unsupported" + ); + } else { + interp_ok(()) + } + } + + /// Check whether the underlying host socket of `socket` contains an error. + /// If there is an error, we store it in `socket.error`. + /// + /// Should `socket` be in the [`SocketState::Connecting`] state whilst there is + /// an error on the host socket, we transition into the [`SocketState::ConnectionFailed`] + /// state because we know that `socket` can no longer successfully establish a + /// connection. + fn update_last_error(&self, socket: &FileDescriptionRef) { + let mut state = socket.state.borrow_mut(); + + let new_error = match &*state { + SocketState::Listening(listener) => + listener.take_error().expect("Reading SO_ERROR should not fail"), + SocketState::Connecting(stream) | SocketState::Connected(stream) => + stream.take_error().expect("Reading SO_ERROR should not fail"), + SocketState::Initial | SocketState::Bound(_) | SocketState::ConnectionFailed(_) => None, + }; + + let Some(new_error) = new_error else { return }; + + // Store the error such that we can return it when + // `getsockopt(SOL_SOCKET, SO_ERROR, ...)` is called on the socket. + socket.error.replace(Some(new_error)); + + if matches!(&*state, SocketState::Connecting(_)) { + // After reading an error on a connecting socket, we know that + // the connection won't be established anymore. By the POSIX + // specification, the socket is now in an unspecified state. + // We thus change the socket state to `ConnectionFailed`. + + // Temporarily use dummy state to take ownership of the stream. + let SocketState::Connecting(stream) = + std::mem::replace(&mut *state, SocketState::Initial) + else { + unreachable!() + }; + *state = SocketState::ConnectionFailed(stream); + } + } } impl VisitProvenance for FileDescriptionRef { @@ -1796,7 +1962,9 @@ impl SourceFileDescription for Socket { let mut state = self.state.borrow_mut(); match &mut *state { SocketState::Listening(listener) => f(listener), - SocketState::Connecting(stream) | SocketState::Connected(stream) => f(stream), + SocketState::Connecting(stream) + | SocketState::Connected(stream) + | SocketState::ConnectionFailed(stream) => f(stream), // We never try adding a socket which is not backed by a real socket to the poll registry. _ => unreachable!(), } diff --git a/src/tools/miri/src/shims/unix/sync.rs b/src/tools/miri/src/shims/unix/sync.rs index 1b65dea982464..4e351c1571218 100644 --- a/src/tools/miri/src/shims/unix/sync.rs +++ b/src/tools/miri/src/shims/unix/sync.rs @@ -920,22 +920,22 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return interp_ok(()); }; - let (clock, anchor) = if macos_relative_np { + let (clock, style) = if macos_relative_np { // `pthread_cond_timedwait_relative_np` always measures time against the // monotonic clock, regardless of the condvar clock. - (TimeoutClock::Monotonic, TimeoutAnchor::Relative) + (TimeoutClock::Monotonic, TimeoutStyle::Relative) } else { if data.clock == TimeoutClock::RealTime { this.check_no_isolation("`pthread_cond_timedwait` with `CLOCK_REALTIME`")?; } - (data.clock, TimeoutAnchor::Absolute) + (data.clock, TimeoutStyle::Absolute) }; this.condvar_wait( data.condvar_ref, mutex_ref, - Some((clock, anchor, duration)), + Some(this.machine.timeout(clock, style, duration)), Scalar::from_i32(0), this.eval_libc("ETIMEDOUT"), // retval_timeout dest.clone(), diff --git a/src/tools/miri/src/shims/windows/sync.rs b/src/tools/miri/src/shims/windows/sync.rs index 14562450e6e07..1b75d6647c831 100644 --- a/src/tools/miri/src/shims/windows/sync.rs +++ b/src/tools/miri/src/shims/windows/sync.rs @@ -209,11 +209,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { }; let size = Size::from_bytes(size); - let timeout = if timeout_ms == this.eval_windows_u32("c", "INFINITE") { + let deadline = if timeout_ms == this.eval_windows_u32("c", "INFINITE") { None } else { let duration = Duration::from_millis(timeout_ms.into()); - Some((TimeoutClock::Monotonic, TimeoutAnchor::Relative, duration)) + Some(this.machine.monotonic_clock.now().add_lossy(duration).into()) }; // See the Linux futex implementation for why this fence exists. @@ -238,7 +238,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.futex_wait( futex_ref, u32::MAX, // bitset - timeout, + deadline, callback!( @capture<'tcx> { dest: MPlaceTy<'tcx> diff --git a/src/tools/miri/tests/fail-dep/libc/socket-connect-after-failed-connection.rs b/src/tools/miri/tests/fail-dep/libc/socket-connect-after-failed-connection.rs new file mode 100644 index 0000000000000..babd2d8410207 --- /dev/null +++ b/src/tools/miri/tests/fail-dep/libc/socket-connect-after-failed-connection.rs @@ -0,0 +1,68 @@ +//@only-target: linux android illumos +//@compile-flags: -Zmiri-disable-isolation + +#![feature(io_error_inprogress)] + +#[path = "../../utils/libc.rs"] +mod libc_utils; + +use std::io::ErrorKind; + +use libc_utils::epoll::*; +use libc_utils::*; + +/// Test calling `connect` after the first `connect` failed. +fn main() { + let client_sockfd = + unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; + let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); + + unsafe { + // Change client socket to be non-blocking. + errno_check(libc::fcntl(client_sockfd, libc::F_SETFL, libc::O_NONBLOCK)); + } + + // We cannot attempt to connect to a localhost address because + // it could be the case that a socket from another test is + // currently listening on `localhost:12321` because we bind to + // random ports everywhere. For `127.0.1.1` we know that it's a loopback + // address and thus exists but because it's not the standard loopback + // address we also assume that nothing is bound to it. + // The port `12321` is just a random non-zero port because Windows + // and Apple hosts return EADDRNOTAVAIL when attempting to connect to + // a zero port. + let addr = net::sock_addr_ipv4([127, 0, 1, 1], 12321); + + // Non-blocking connect should fail with EINPROGRESS. + let err = net::connect_ipv4(client_sockfd, addr).unwrap_err(); + assert_eq!(err.kind(), ErrorKind::InProgress); + + // Add interest for client socket. + epoll_ctl_add(epfd, client_sockfd, EPOLLOUT | EPOLLET | libc::EPOLLERR).unwrap(); + + // Wait until the socket has an error. + check_epoll_wait::<8>( + epfd, + &[Ev { events: libc::EPOLLERR | EPOLLOUT | EPOLLHUP, data: client_sockfd }], + -1, + ); + + let errno = + net::getsockopt::(client_sockfd, libc::SOL_SOCKET, libc::SO_ERROR).unwrap(); + // Depending on the host we receive different error kinds. Thus, we only check + // that it's a nonzero error code. + assert!(errno != 0); + + // Ensure that error readiness is cleared after reading SO_ERROR. + let readiness = current_epoll_readiness::<8>(client_sockfd, EPOLLET | EPOLLOUT | EPOLLERR); + assert!(readiness & EPOLLERR == 0); + + unsafe { + //~vERROR: sockets are in an unspecified state after a failed `connect` + libc::connect( + client_sockfd, + (&addr as *const libc::sockaddr_in).cast(), + size_of::() as libc::socklen_t, + ) + }; +} diff --git a/src/tools/miri/tests/fail-dep/libc/socket-connect-after-failed-connection.stderr b/src/tools/miri/tests/fail-dep/libc/socket-connect-after-failed-connection.stderr new file mode 100644 index 0000000000000..3743c3ca58f22 --- /dev/null +++ b/src/tools/miri/tests/fail-dep/libc/socket-connect-after-failed-connection.stderr @@ -0,0 +1,16 @@ +error: unsupported operation: connect: sockets are in an unspecified state after a failed `connect`; any operation on such a socket is thus unsupported + --> tests/fail-dep/libc/socket-connect-after-failed-connection.rs:LL:CC + | +LL | / libc::connect( +LL | | client_sockfd, +LL | | (&addr as *const libc::sockaddr_in).cast(), +LL | | size_of::() as libc::socklen_t, +LL | | ) + | |_________^ unsupported operation occurred here + | + = help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/tree_borrows/cell-inside-struct.tree.stderr b/src/tools/miri/tests/fail/tree_borrows/cell-inside-struct.tree.stderr index 282dc19eea791..bdd2241c70707 100644 --- a/src/tools/miri/tests/fail/tree_borrows/cell-inside-struct.tree.stderr +++ b/src/tools/miri/tests/fail/tree_borrows/cell-inside-struct.tree.stderr @@ -1,7 +1,7 @@ ────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 4.. 8 -| Act | Act | └─┬── +| Unq | Unq | └─┬── | Frz |?Cel | └──── ────────────────────────────────────────────────── error: Undefined Behavior: write access through (a) at ALLOC[0x0] is forbidden diff --git a/src/tools/miri/tests/fail/tree_borrows/cell-inside-struct.tree_implicit_writes.stderr b/src/tools/miri/tests/fail/tree_borrows/cell-inside-struct.tree_implicit_writes.stderr index 282dc19eea791..bdd2241c70707 100644 --- a/src/tools/miri/tests/fail/tree_borrows/cell-inside-struct.tree_implicit_writes.stderr +++ b/src/tools/miri/tests/fail/tree_borrows/cell-inside-struct.tree_implicit_writes.stderr @@ -1,7 +1,7 @@ ────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 4.. 8 -| Act | Act | └─┬── +| Unq | Unq | └─┬── | Frz |?Cel | └──── ────────────────────────────────────────────────── error: Undefined Behavior: write access through (a) at ALLOC[0x0] is forbidden diff --git a/src/tools/miri/tests/fail/tree_borrows/reserved/cell-protected-write.stderr b/src/tools/miri/tests/fail/tree_borrows/reserved/cell-protected-write.stderr index 29397a8fd0bc7..f1bb93354ff90 100644 --- a/src/tools/miri/tests/fail/tree_borrows/reserved/cell-protected-write.stderr +++ b/src/tools/miri/tests/fail/tree_borrows/reserved/cell-protected-write.stderr @@ -1,7 +1,7 @@ ────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 -| Act | └─┬── +| Unq | └─┬── | ReIM| └─┬── | ReIM| ├─┬── | ReIM| │ └─┬── diff --git a/src/tools/miri/tests/fail/tree_borrows/reserved/int-protected-write.stderr b/src/tools/miri/tests/fail/tree_borrows/reserved/int-protected-write.stderr index 3bddd2ce1de63..c86d70bcba287 100644 --- a/src/tools/miri/tests/fail/tree_borrows/reserved/int-protected-write.stderr +++ b/src/tools/miri/tests/fail/tree_borrows/reserved/int-protected-write.stderr @@ -1,7 +1,7 @@ ────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 -| Act | └─┬── +| Unq | └─┬── | Res | └─┬── | Res | ├─┬── | Res | │ └─┬── 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 4793ee560fca4..f162c306784d0 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-affinity.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-affinity.rs @@ -1,7 +1,5 @@ //@only-target: linux freebsd # these are Linux/FreeBSD-specific APIs //@compile-flags: -Zmiri-disable-isolation -Zmiri-num-cpus=4 -#![feature(io_error_more)] -#![feature(pointer_is_aligned_to)] use std::mem::{size_of, size_of_val}; 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 d9a2be7fc5014..8865c605a3b65 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 @@ -89,7 +89,7 @@ fn test_notification_after_timeout() { check_epoll_wait::<1>(epfd, &[Ev { events: libc::EPOLLIN | libc::EPOLLOUT, data: fds[0] }], 10); } -// This test shows a data_race before epoll had vector clocks added. +// This test shows a data race before epoll had vector clocks added. fn test_epoll_race() { // Create an epoll instance. let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); @@ -99,7 +99,7 @@ fn test_epoll_race() { let fd = errno_result(unsafe { libc::eventfd(0, flags) }).unwrap(); // Register eventfd with the epoll instance. - epoll_ctl_add(epfd, fd, libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).unwrap(); + epoll_ctl_add(epfd, fd, libc::EPOLLIN | libc::EPOLLET).unwrap(); static mut VAL: u8 = 0; let thread1 = thread::spawn(move || { @@ -109,13 +109,10 @@ fn test_epoll_race() { write_all(fd, &1_u64.to_ne_bytes()).unwrap(); }); thread::yield_now(); - // epoll_wait for the event to happen. - check_epoll_wait::<8>(epfd, &[Ev { events: (libc::EPOLLIN | libc::EPOLLOUT), data: fd }], -1); + // epoll_wait for EPOLLIN. + check_epoll_wait::<8>(epfd, &[Ev { events: libc::EPOLLIN, data: fd }], -1); // Read from the static mut variable. - #[allow(static_mut_refs)] - unsafe { - assert_eq!(VAL, 1) - }; + assert_eq!(unsafe { VAL }, 1); thread1.join().unwrap(); } 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 56d215d0ed633..e86c70b590b36 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-eventfd.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-eventfd.rs @@ -5,7 +5,10 @@ // FIXME(static_mut_refs): Do not allow `static_mut_refs` lint #![allow(static_mut_refs)] -use std::thread; +use std::{io, thread}; + +#[path = "../../utils/libc.rs"] +mod libc_utils; fn main() { test_read_write(); @@ -19,15 +22,16 @@ fn main() { test_two_threads_blocked_on_eventfd(); } -fn read_bytes(fd: i32, buf: &mut [u8; N]) -> i32 { - let res: i32 = unsafe { libc::read(fd, buf.as_mut_ptr().cast(), N).try_into().unwrap() }; - return res; +// We want to do individual read/write calls here so we avoid read_exact/write_all. + +fn read_bytes(fd: i32, buf: &mut [u8; N]) -> io::Result { + libc_utils::errno_result(unsafe { libc::read(fd, buf.as_mut_ptr().cast(), N) }) + .map(|len| len.try_into().unwrap()) } -fn write_bytes(fd: i32, data: [u8; N]) -> i32 { - let res: i32 = - unsafe { libc::write(fd, data.as_ptr() as *const libc::c_void, N).try_into().unwrap() }; - return res; +fn write_bytes(fd: i32, data: [u8; N]) -> io::Result { + libc_utils::errno_result(unsafe { libc::write(fd, data.as_ptr() as *const libc::c_void, N) }) + .map(|len| len.try_into().unwrap()) } fn test_read_write() { @@ -35,64 +39,60 @@ fn test_read_write() { let fd = unsafe { libc::eventfd(0, flags) }; let sized_8_data: [u8; 8] = 1_u64.to_ne_bytes(); // Write 1 to the counter. - let res = write_bytes(fd, sized_8_data); + let res = write_bytes(fd, sized_8_data).unwrap(); + assert_eq!(res, 8); + // Write 0 to the counter, + let res = write_bytes(fd, [0u8; 8]).unwrap(); assert_eq!(res, 8); // Read 1 from the counter. let mut buf: [u8; 8] = [0; 8]; - let res = read_bytes(fd, &mut buf); + let res = read_bytes(fd, &mut buf).unwrap(); // Read returns number of bytes has been read, which is always 8. assert_eq!(res, 8); // Check the value of counter read. - let counter = u64::from_ne_bytes(buf); - assert_eq!(counter, 1); + assert_eq!(u64::from_ne_bytes(buf), 1); - // After read, the counter is currently 0, read counter 0 should fail with return - // value -1. + // After read, the counter is currently 0, read counter 0 should fail with EAGAIN. let mut buf: [u8; 8] = [0; 8]; - let res = read_bytes(fd, &mut buf); - let e = std::io::Error::last_os_error(); - assert_eq!(e.raw_os_error(), Some(libc::EAGAIN)); - assert_eq!(res, -1); - - // Write with supplied buffer bigger than 8 bytes should be allowed. - let sized_9_data: [u8; 9]; - if cfg!(target_endian = "big") { - // Adjust the data based on the endianness of host system. - sized_9_data = [0, 0, 0, 0, 0, 0, 0, 1, 0]; - } else { - sized_9_data = [1, 0, 0, 0, 0, 0, 0, 0, 0]; - } - let res = write_bytes(fd, sized_9_data); - assert_eq!(res, 8); + let err = read_bytes(fd, &mut buf).unwrap_err(); + assert_eq!(err.raw_os_error(), Some(libc::EAGAIN)); + + // Write with supplied buffer bigger than 8 bytes should be allowed according to the docs, + // but tests on real systems indicate that it fails. + let sized_9_data = [0u8; 9]; + let err = write_bytes(fd, sized_9_data).unwrap_err(); + assert_eq!(err.raw_os_error(), Some(libc::EINVAL)); // Read with supplied buffer smaller than 8 bytes should fail with return // value -1. let mut buf: [u8; 7] = [1; 7]; - let res = read_bytes(fd, &mut buf); - let e = std::io::Error::last_os_error(); - assert_eq!(e.raw_os_error(), Some(libc::EINVAL)); - assert_eq!(res, -1); + let err = read_bytes(fd, &mut buf).unwrap_err(); + assert_eq!(err.raw_os_error(), Some(libc::EINVAL)); // Write with supplied buffer smaller than 8 bytes should fail with return // value -1. let size_7_data: [u8; 7] = [1; 7]; - let res = write_bytes(fd, size_7_data); - let e = std::io::Error::last_os_error(); - assert_eq!(e.raw_os_error(), Some(libc::EINVAL)); - assert_eq!(res, -1); + let err = write_bytes(fd, size_7_data).unwrap_err(); + assert_eq!(err.raw_os_error(), Some(libc::EINVAL)); + + // Do two valid writes so we have something to read for the next test. + let res = write_bytes(fd, sized_8_data).unwrap(); + assert_eq!(res, 8); + let res = write_bytes(fd, sized_8_data).unwrap(); + assert_eq!(res, 8); // Read with supplied buffer bigger than 8 bytes should be allowed. let mut buf: [u8; 9] = [1; 9]; - let res = read_bytes(fd, &mut buf); + let res = read_bytes(fd, &mut buf).unwrap(); assert_eq!(res, 8); + let buf: &[u8; 8] = (&buf[..8]).try_into().unwrap(); + assert_eq!(u64::from_ne_bytes(*buf), 2); // Write u64::MAX should fail. let u64_max_bytes: [u8; 8] = [255; 8]; - let res = write_bytes(fd, u64_max_bytes); - let e = std::io::Error::last_os_error(); - assert_eq!(e.raw_os_error(), Some(libc::EINVAL)); - assert_eq!(res, -1); + let err = write_bytes(fd, u64_max_bytes).unwrap_err(); + assert_eq!(err.raw_os_error(), Some(libc::EINVAL)); } fn test_race() { @@ -101,7 +101,7 @@ fn test_race() { let fd = unsafe { libc::eventfd(0, flags) }; let thread1 = thread::spawn(move || { let mut buf: [u8; 8] = [0; 8]; - let res = read_bytes(fd, &mut buf); + let res = read_bytes(fd, &mut buf).unwrap(); // read returns number of bytes has been read, which is always 8. assert_eq!(res, 8); let counter = u64::from_ne_bytes(buf); @@ -110,7 +110,7 @@ fn test_race() { }); unsafe { VAL = 1 }; let data: [u8; 8] = 1_u64.to_ne_bytes(); - let res = write_bytes(fd, data); + let res = write_bytes(fd, data).unwrap(); // write returns number of bytes written, which is always 8. assert_eq!(res, 8); thread::yield_now(); @@ -135,7 +135,7 @@ fn test_blocking_read() { let thread1 = thread::spawn(move || { let mut buf: [u8; 8] = [0; 8]; // This will block. - let res = read_bytes(fd, &mut buf); + let res = read_bytes(fd, &mut buf).unwrap(); // read returns number of bytes has been read, which is always 8. assert_eq!(res, 8); let counter = u64::from_ne_bytes(buf); @@ -145,7 +145,7 @@ fn test_blocking_read() { // Pass control to thread1 so it can block on eventfd `read`. thread::yield_now(); // Write 1 to the counter to unblock thread1. - let res = write_bytes(fd, sized_8_data); + let res = write_bytes(fd, sized_8_data).unwrap(); assert_eq!(res, 8); thread1.join().unwrap(); } @@ -176,7 +176,7 @@ fn test_blocking_write() { // Pass control to thread1 so it can block on eventfd `write`. thread::yield_now(); // This will unblock previously blocked eventfd read. - let res = read_bytes(fd, &mut buf); + let res = read_bytes(fd, &mut buf).unwrap(); // read returns number of bytes has been read, which is always 8. assert_eq!(res, 8); let counter = u64::from_ne_bytes(buf); @@ -222,7 +222,7 @@ fn test_two_threads_blocked_on_eventfd() { let thread3 = thread::spawn(move || { let mut buf: [u8; 8] = [0; 8]; // This will unblock previously blocked eventfd read. - let res = read_bytes(fd, &mut buf); + let res = read_bytes(fd, &mut buf).unwrap(); // read returns number of bytes has been read, which is always 8. assert_eq!(res, 8); let counter = u64::from_ne_bytes(buf); diff --git a/src/tools/miri/tests/pass-dep/libc/libc-fs-permissions.rs b/src/tools/miri/tests/pass-dep/libc/libc-fs-permissions.rs index f03e1ad8641de..fd67afbe83c4b 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-fs-permissions.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-fs-permissions.rs @@ -2,9 +2,6 @@ //@ignore-host: windows # needs unix PermissionExt //@compile-flags: -Zmiri-disable-isolation -#![feature(io_error_more)] -#![feature(io_error_uncategorized)] - use std::ffi::{CStr, CString}; use std::mem::MaybeUninit; use std::os::unix::ffi::OsStrExt; 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 a388943f922ff..9cc8685827077 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs @@ -1,15 +1,13 @@ //@ignore-target: windows # no libc //@compile-flags: -Zmiri-disable-isolation -#![feature(io_error_more)] -#![feature(io_error_uncategorized)] - use std::ffi::{CStr, CString, OsString}; -use std::fs::{File, canonicalize, remove_file}; +use std::fs::{File, canonicalize, create_dir, remove_dir, remove_file}; use std::io::{Error, ErrorKind, Write}; use std::os::unix::ffi::OsStrExt; use std::os::unix::io::AsRawFd; use std::path::PathBuf; +use std::ptr; #[path = "../../utils/mod.rs"] mod utils; @@ -17,6 +15,8 @@ mod utils; #[path = "../../utils/libc.rs"] mod libc_utils; +use libc_utils::errno_result; + fn main() { test_dup(); test_dup_stdout_stderr(); @@ -58,6 +58,16 @@ fn main() { test_statx_on_file_descriptor(); #[cfg(target_os = "linux")] test_statx_empty_path_on_pipe(); + test_readv(); + test_readv_empty_bufs(); + #[cfg(not(target_os = "solaris"))] + test_preadv(); + test_pread(); + test_writev(); + test_writev_empty_bufs(); + #[cfg(not(target_os = "solaris"))] + test_pwritev(); + test_pwrite(); } #[cfg(target_os = "linux")] @@ -67,10 +77,12 @@ fn assert_statx_matches_metadata(stx: &libc::statx, meta: &std::fs::Metadata, ex let mask = stx.stx_mask; // Guaranteed by the shim on any Linux target. - assert!(mask & libc::STATX_TYPE != 0); assert!(mask & libc::STATX_SIZE != 0); assert_eq!(stx.stx_size, expected_size); - assert_eq!((stx.stx_mode as u32) & libc::S_IFMT, libc::S_IFREG); + assert!(mask & libc::STATX_TYPE != 0); + assert_eq!((stx.stx_mode as libc::mode_t) & libc::S_IFMT, libc::S_IFREG); + assert!(mask & libc::STATX_MODE != 0); + assert_ne!((stx.stx_mode as libc::mode_t) & !libc::S_IFMT, 0); // Host-dependent enrichment: only assert when the mask says the field is real. if mask & libc::STATX_INO != 0 { @@ -89,9 +101,6 @@ fn assert_statx_matches_metadata(stx: &libc::statx, meta: &std::fs::Metadata, ex assert_eq!(stx.stx_blocks, meta.blocks()); } - // We don't support non-S_IFMT bits in stx_mode. - assert_eq!(mask & libc::STATX_MODE, 0); - // Do not assert stx_blksize and stx_dev_* : there are no mask bits for them. } @@ -177,18 +186,21 @@ fn test_statx_empty_path_on_pipe() { let statx_buf = statx_buf.assume_init(); - assert_ne!(statx_buf.stx_mask & libc::STATX_TYPE, 0); assert_ne!(statx_buf.stx_mask & libc::STATX_SIZE, 0); - assert_eq!(statx_buf.stx_mask & libc::STATX_MODE, 0); - assert_eq!((statx_buf.stx_mode as libc::mode_t) & libc::S_IFMT, libc::S_IFIFO); assert_eq!(statx_buf.stx_size, 0); - - // Synthetic metadata must not advertise host-only fields. - assert_eq!(statx_buf.stx_mask & libc::STATX_INO, 0); - assert_eq!(statx_buf.stx_mask & libc::STATX_NLINK, 0); - assert_eq!(statx_buf.stx_mask & libc::STATX_UID, 0); - assert_eq!(statx_buf.stx_mask & libc::STATX_GID, 0); - assert_eq!(statx_buf.stx_mask & libc::STATX_BLOCKS, 0); + assert_ne!(statx_buf.stx_mask & libc::STATX_TYPE, 0); + assert_eq!((statx_buf.stx_mode as libc::mode_t) & libc::S_IFMT, libc::S_IFIFO); + assert_ne!(statx_buf.stx_mask & libc::STATX_MODE, 0); + assert_ne!((statx_buf.stx_mode as libc::mode_t) & !libc::S_IFMT, 0); + + if cfg!(miri) { + // Synthetic metadata must not advertise host-only fields. + assert_eq!(statx_buf.stx_mask & libc::STATX_INO, 0); + assert_eq!(statx_buf.stx_mask & libc::STATX_NLINK, 0); + assert_eq!(statx_buf.stx_mask & libc::STATX_UID, 0); + assert_eq!(statx_buf.stx_mask & libc::STATX_GID, 0); + assert_eq!(statx_buf.stx_mask & libc::STATX_BLOCKS, 0); + } errno_check(libc::close(fds[0])); errno_check(libc::close(fds[1])); @@ -325,13 +337,16 @@ fn test_ftruncate>( #[cfg(target_os = "linux")] fn test_o_tmpfile_flag() { + if !cfg!(miri) { + return; // checks miri-specific behavior + } + use std::fs::{OpenOptions, create_dir}; use std::os::unix::fs::OpenOptionsExt; let dir_path = utils::prepare_dir("miri_test_fs_dir"); create_dir(&dir_path).unwrap(); // test that the `O_TMPFILE` custom flag gracefully errors instead of stopping execution assert_eq!( - Some(libc::EOPNOTSUPP), OpenOptions::new() .read(true) .write(true) @@ -339,14 +354,22 @@ fn test_o_tmpfile_flag() { .open(dir_path) .unwrap_err() .raw_os_error(), + Some(libc::EOPNOTSUPP), ); } fn test_posix_mkstemp() { + use std::env; use std::ffi::OsStr; use std::os::unix::io::FromRawFd; use std::path::Path; + // We want to test `mkstemp` on a relative name, so we cd to a tempdir and later cd back. + let old_cwd = env::current_dir().unwrap(); + let dir_path = utils::prepare_dir("miri_test_libc_readdir"); + create_dir(&dir_path).expect("create_dir failed"); + env::set_current_dir(&dir_path).unwrap(); + let valid_template = "fooXXXXXX"; // C needs to own this as `mkstemp(3)` says: // "Since it will be modified, `template` must not be a string constant, but @@ -354,12 +377,12 @@ fn test_posix_mkstemp() { // There seems to be no `as_mut_ptr` on `CString` so we need to use `into_raw`. let ptr = CString::new(valid_template).unwrap().into_raw(); let fd = unsafe { libc::mkstemp(ptr) }; + assert!(fd >= 0, "mkstemp failed"); // Take ownership back in Rust to not leak memory. let slice = unsafe { CString::from_raw(ptr) }; - assert!(fd > 0); let osstr = OsStr::from_bytes(slice.to_bytes()); let path: &Path = osstr.as_ref(); - let name = path.file_name().unwrap().to_string_lossy(); + let name = path.to_string_lossy(); assert!(name.ne("fooXXXXXX")); assert!(name.starts_with("foo")); assert_eq!(name.len(), 9); @@ -369,6 +392,9 @@ fn test_posix_mkstemp() { ); let file = unsafe { File::from_raw_fd(fd) }; assert!(file.set_len(0).is_ok()); + // Cleanup. Also checks that the filename actually exists. + drop(file); + remove_file(path).unwrap(); let invalid_templates = vec!["foo", "barXX", "XXXXXXbaz", "whatXXXXXXever", "X"]; for t in invalid_templates { @@ -382,6 +408,8 @@ fn test_posix_mkstemp() { assert_eq!(e.raw_os_error(), Some(libc::EINVAL)); assert_eq!(e.kind(), std::io::ErrorKind::InvalidInput); } + + env::set_current_dir(old_cwd).unwrap(); } /// Test allocating variant of `realpath`. @@ -751,7 +779,6 @@ fn test_ioctl() { fn test_opendir_closedir() { // dir should exist - use std::fs::{create_dir, remove_dir}; let path = utils::prepare_dir("miri_test_libc_opendir_closedir"); create_dir(&path).expect("create_dir failed"); let cpath = CString::new(path.as_os_str().as_bytes()).expect("CString::new failed"); @@ -846,3 +873,277 @@ pub fn check_stat_fields(stat: &libc::stat) { let _st_mtime_nsec = stat.st_mtime_nsec; let _st_ctime_nsec = stat.st_ctime_nsec; } + +/// Test vectored reads with multiple buffers. +fn test_readv() { + let file_contents = [1u8, 2, 3, 4, 5, 6]; + let path = utils::prepare_with_content("pass-libc-readv.txt", &file_contents); + let cpath = CString::new(path.into_os_string().into_encoded_bytes()).unwrap(); + let fd = unsafe { libc::open(cpath.as_ptr(), libc::O_RDONLY) }; + assert_ne!(fd, -1); + + let mut buffer = [0u8; 4]; + let (buffer1, buffer2) = buffer.split_at_mut(2); + + let iov = [ + libc::iovec { iov_base: ptr::null_mut::(), iov_len: 0 as libc::size_t }, + libc::iovec { + iov_base: buffer1.as_mut_ptr().cast::(), + iov_len: buffer1.len() as libc::size_t, + }, + libc::iovec { + iov_base: buffer2.as_mut_ptr().cast::(), + iov_len: buffer2.len() as libc::size_t, + }, + ]; + + let bytes_read = unsafe { + errno_result(libc::readv(fd, iov.as_ptr(), iov.len() as libc::c_int)).unwrap() as usize + }; + + // The vectored read should read at least one byte. + assert!(bytes_read > 0); + assert_eq!(&buffer[0..bytes_read], &file_contents[0..bytes_read]); +} + +/// Test that vectored reads without any buffers return zero. +fn test_readv_empty_bufs() { + let path = utils::prepare_with_content("pass-libc-readv-empty-bufs.txt", &[1u8, 2, 3]); + let cpath = CString::new(path.into_os_string().into_encoded_bytes()).unwrap(); + let fd = unsafe { libc::open(cpath.as_ptr(), libc::O_RDONLY) }; + assert_ne!(fd, -1); + unsafe { assert_eq!(errno_result(libc::readv(fd, ptr::null::(), 0)).unwrap(), 0) }; +} + +/// Test vectored reads with multiple buffers and a byte offset. +/// +/// **Note**: We skip this test on Solaris targets because Solaris +/// doesn't have `preadv`. +#[cfg(not(target_os = "solaris"))] +fn test_preadv() { + let file_contents = [1u8, 2, 3, 4, 5, 6]; + let path = utils::prepare_with_content("pass-libc-preadv.txt", &file_contents); + let cpath = CString::new(path.into_os_string().into_encoded_bytes()).unwrap(); + let fd = unsafe { libc::open(cpath.as_ptr(), libc::O_RDONLY) }; + assert_ne!(fd, -1); + + let mut buffer = [0u8; 4]; + let (buffer1, buffer2) = buffer.split_at_mut(2); + + let iov = [ + libc::iovec { iov_base: ptr::null_mut::(), iov_len: 0 as libc::size_t }, + libc::iovec { + iov_base: buffer1.as_mut_ptr().cast::(), + iov_len: buffer1.len() as libc::size_t, + }, + libc::iovec { + iov_base: buffer2.as_mut_ptr().cast::(), + iov_len: buffer2.len() as libc::size_t, + }, + ]; + + // Read with a 2 byte offset. + const OFFSET: usize = 2; + let bytes_read = unsafe { + errno_result(libc::preadv( + fd, + iov.as_ptr(), + iov.len() as libc::c_int, + OFFSET as libc::off_t, + )) + .unwrap() as usize + }; + + // The vectored read should read at least one byte. + assert!(bytes_read > 0); + // The vectored read should start at the provided byte offset. + assert_eq!(&buffer[0..bytes_read], &file_contents[OFFSET..(bytes_read + OFFSET)]); +} + +/// Test reading with an offset. +fn test_pread() { + let file_contents = [1u8, 2, 3, 4, 5, 6]; + let path = utils::prepare_with_content("pass-libc-pread.txt", &file_contents); + let cpath = CString::new(path.into_os_string().into_encoded_bytes()).unwrap(); + let fd = unsafe { libc::open(cpath.as_ptr(), libc::O_RDONLY) }; + assert_ne!(fd, -1); + + let mut buffer = [0u8; 2]; + + // Read with a 2 byte offset. + const OFFSET: usize = 2; + let bytes_read = unsafe { + errno_result(libc::pread( + fd, + buffer.as_mut_ptr().cast(), + buffer.len() as libc::size_t, + OFFSET as libc::off_t, + )) + .unwrap() as usize + }; + + // We should read at least one byte. + assert!(bytes_read > 0); + // The read should start at the provided byte offset. + assert_eq!(&buffer[0..bytes_read], &file_contents[OFFSET..(bytes_read + OFFSET)]); +} + +/// Test vectored writes with multiple buffers. +fn test_writev() { + let path = utils::prepare_with_content("pass-libc-writev.txt", &[]); + let cpath = CString::new(path.into_os_string().into_encoded_bytes()).unwrap(); + let fd = unsafe { libc::open(cpath.as_ptr(), libc::O_WRONLY) }; + assert_ne!(fd, -1); + + let mut write_buffer = [1u8, 2, 3, 4, 5, 6]; + let (buffer1, buffer2) = write_buffer.split_at_mut(3); + + let iov = [ + libc::iovec { iov_base: ptr::null_mut::(), iov_len: 0 as libc::size_t }, + libc::iovec { + iov_base: buffer1.as_mut_ptr().cast::(), + iov_len: buffer1.len() as libc::size_t, + }, + libc::iovec { + iov_base: buffer2.as_mut_ptr().cast::(), + iov_len: buffer2.len() as libc::size_t, + }, + ]; + + let bytes_written = unsafe { + errno_result(libc::writev(fd, iov.as_ptr(), iov.len() as libc::c_int)).unwrap() as usize + }; + // The vectored write should write at least one byte. + assert!(bytes_written > 0); + + // Open the FD again in readonly mode and with an unadvanced pointer. + let fd = unsafe { libc::open(cpath.as_ptr(), libc::O_RDONLY) }; + assert_ne!(fd, -1); + + let mut read_buffer = [0u8; 16]; + unsafe { + libc_utils::read_exact_generic( + read_buffer.as_mut_ptr().cast(), + bytes_written as libc::size_t, + libc_utils::Retry::NoRetry, + |buf, count| libc::read(fd, buf, count), + ) + .unwrap() + }; + + assert_eq!(&write_buffer[0..bytes_written], &read_buffer[0..bytes_written]); +} + +/// Test that vectored writes without any buffers return zero. +fn test_writev_empty_bufs() { + let path = utils::prepare_with_content("pass-libc-writev-empty-bufs.txt", &[1u8, 2, 3]); + let cpath = CString::new(path.into_os_string().into_encoded_bytes()).unwrap(); + let fd = unsafe { libc::open(cpath.as_ptr(), libc::O_WRONLY) }; + assert_ne!(fd, -1); + unsafe { + assert_eq!(errno_result(libc::writev(fd, ptr::null::(), 0)).unwrap(), 0) + }; +} + +/// Test vectored writes with multiple buffers and a byte offset. +/// +/// **Note**: We skip this test on Solaris targets because Solaris +/// doesn't have `pwritev`. +#[cfg(not(target_os = "solaris"))] +fn test_pwritev() { + let path = utils::prepare_with_content("pass-libc-pwritev.txt", &[]); + let cpath = CString::new(path.into_os_string().into_encoded_bytes()).unwrap(); + let fd = unsafe { libc::open(cpath.as_ptr(), libc::O_WRONLY) }; + assert_ne!(fd, -1); + + let mut write_buffer = [1u8, 2, 3, 4, 5, 6]; + let (buffer1, buffer2) = write_buffer.split_at_mut(3); + + let iov = [ + libc::iovec { iov_base: ptr::null_mut::(), iov_len: 0 as libc::size_t }, + libc::iovec { + iov_base: buffer1.as_mut_ptr().cast::(), + iov_len: buffer1.len() as libc::size_t, + }, + libc::iovec { + iov_base: buffer2.as_mut_ptr().cast::(), + iov_len: buffer2.len() as libc::size_t, + }, + ]; + + // Write with a 2 byte offset. + const OFFSET: usize = 2; + let bytes_written = unsafe { + errno_result(libc::pwritev( + fd, + iov.as_ptr(), + iov.len() as libc::c_int, + OFFSET as libc::off_t, + )) + .unwrap() as usize + }; + // The vectored write should write at least one byte. + assert!(bytes_written > 0); + + // Open the FD again in readonly mode and with an unadvanced pointer. + let fd = unsafe { libc::open(cpath.as_ptr(), libc::O_RDONLY) }; + assert_ne!(fd, -1); + + let mut read_buffer = [0u8; 16]; + // Read offset + bytes written. + unsafe { + libc_utils::read_exact_generic( + read_buffer.as_mut_ptr().cast(), + (bytes_written + OFFSET) as libc::size_t, + libc_utils::Retry::NoRetry, + |buf, count| libc::read(fd, buf, count), + ) + .unwrap() + }; + + // The vectored write should start at the provided byte offset. + assert_eq!(&write_buffer[0..bytes_written], &read_buffer[OFFSET..(bytes_written + OFFSET)]); +} + +/// Test writing with an offset. +fn test_pwrite() { + let path = utils::prepare_with_content("pass-libc-pwritev.txt", &[]); + let cpath = CString::new(path.into_os_string().into_encoded_bytes()).unwrap(); + let fd = unsafe { libc::open(cpath.as_ptr(), libc::O_WRONLY) }; + assert_ne!(fd, -1); + + let write_buffer = [1u8, 2, 3, 4, 5, 6]; + + // Write with a 2 byte offset. + const OFFSET: usize = 2; + let bytes_written = unsafe { + errno_result(libc::pwrite( + fd, + write_buffer.as_ptr().cast(), + write_buffer.len() as libc::size_t, + OFFSET as libc::off_t, + )) + .unwrap() as usize + }; + // We should write at least one byte. + assert!(bytes_written > 0); + + // Open the FD again in readonly mode and with an unadvanced pointer. + let fd = unsafe { libc::open(cpath.as_ptr(), libc::O_RDONLY) }; + assert_ne!(fd, -1); + + let mut read_buffer = [0u8; 16]; + // Read offset + bytes written. + unsafe { + libc_utils::read_exact_generic( + read_buffer.as_mut_ptr().cast(), + (bytes_written + OFFSET) as libc::size_t, + libc_utils::Retry::NoRetry, + |buf, count| libc::read(fd, buf, count), + ) + .unwrap() + }; + + // The write should start at the provided byte offset. + assert_eq!(&write_buffer[0..bytes_written], &read_buffer[OFFSET..(bytes_written + OFFSET)]); +} diff --git a/src/tools/miri/tests/pass-dep/libc/libc-misc.rs b/src/tools/miri/tests/pass-dep/libc/libc-misc.rs index 1eac3d25e56a1..10d756e05104b 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-misc.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-misc.rs @@ -1,7 +1,5 @@ //@ignore-target: windows # only very limited libc on Windows //@compile-flags: -Zmiri-disable-isolation -#![feature(io_error_more)] -#![feature(pointer_is_aligned_to)] use std::mem::transmute; diff --git a/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking-epoll.rs b/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking-epoll.rs index 08f1ee91b1b1f..9c8c5be647d4c 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking-epoll.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking-epoll.rs @@ -25,6 +25,7 @@ fn main() { test_shutdown_read(); test_shutdown_write(); test_readiness_after_short_read(); + test_readiness_after_short_peek(); test_readiness_after_short_write(); } @@ -505,6 +506,55 @@ fn test_readiness_after_short_read() { }; } +/// Test that Miri doesn't remove the readable readiness after a short peek. +fn test_readiness_after_short_peek() { + let (server_sockfd, addr) = net::make_listener_ipv4().unwrap(); + let client_sockfd = + unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; + + net::connect_ipv4(client_sockfd, addr).unwrap(); + let (peerfd, _) = net::accept_ipv4(server_sockfd).unwrap(); + + unsafe { + // Change client socket to be non-blocking. + errno_check(libc::fcntl(client_sockfd, libc::F_SETFL, libc::O_NONBLOCK)); + } + + // Write some bytes into the peer socket. + libc_utils::write_all(peerfd, TEST_BYTES).unwrap(); + + // FIXME: Changes in host I/O readiness are only processed when entering the scheduler. + // Ensure that we process the effects if the `write_all` by yielding the current (only) thread. + // + thread::yield_now(); + + // `buffer` is intentionally bigger than `TEST_BYTES.len()` to trigger a short peek. + let mut buffer = [0; 128]; + let bytes_read = unsafe { + errno_result(libc::recv( + client_sockfd, + buffer.as_mut_ptr().cast(), + buffer.len(), + libc::MSG_PEEK, + )) + .unwrap() + } as usize; + assert_eq!(bytes_read, TEST_BYTES.len()); + + // FIXME(#5047): same as above. + thread::yield_now(); + + // Ensure that the readable readiness is still set. + assert_eq!(current_epoll_readiness::<8>(client_sockfd, EPOLLIN | EPOLLET), EPOLLIN); + + // We should be able to read the buffer without blocking indefinitely. + let bytes_read = unsafe { + errno_result(libc::recv(client_sockfd, buffer.as_mut_ptr().cast(), buffer.len(), 0)) + .unwrap() + } as usize; + assert_eq!(bytes_read, TEST_BYTES.len()); +} + /// Test that Miri correctly removes the writable readiness or emits a new edge after a short write. fn test_readiness_after_short_write() { let (server_sockfd, addr) = net::make_listener_ipv4().unwrap(); diff --git a/src/tools/miri/tests/pass-dep/libc/libc-socket.rs b/src/tools/miri/tests/pass-dep/libc/libc-socket.rs index d9200c818608c..1ecfba3ff6ff0 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-socket.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-socket.rs @@ -7,8 +7,8 @@ mod libc_utils; mod utils; use std::io::ErrorKind; -use std::thread; use std::time::Duration; +use std::{ptr, thread}; use libc_utils::*; @@ -35,8 +35,11 @@ fn main() { test_listen(); test_accept_connect(); + test_connect_error(); test_send_peek_recv(); test_write_read(); + test_readv(); + test_writev(); test_getsockname_ipv4(); test_getsockname_ipv4_random_port(); @@ -256,6 +259,27 @@ fn test_accept_connect() { server_thread.join().unwrap(); } +/// Test connecting to an address where nothing is listening and ensure the error matches what +/// the standard library expects. +fn test_connect_error() { + let client_sockfd = + unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; + + // Connecting to a zero port fails on all host platforms. + let addr = net::sock_addr_ipv4(net::IPV4_LOCALHOST, 0); + + let err = net::connect_ipv4(client_sockfd, addr).unwrap_err(); + // Ensure that we fail for the same reasons as the standard library expects. + assert!(matches!( + err.kind(), + ErrorKind::ConnectionRefused + | ErrorKind::InvalidInput + | ErrorKind::AddrInUse + | ErrorKind::AddrNotAvailable + | ErrorKind::NetworkUnreachable + )); +} + /// Test sending bytes into a connected stream and then peeking and receiving /// them from the other end. /// We especially want to test that the peeking doesn't remove the bytes from @@ -342,6 +366,77 @@ fn test_write_read() { server_thread.join().unwrap(); } +/// Test vectored reads with multiple buffers on a connected socket. +fn test_readv() { + let (server_sockfd, addr) = net::make_listener_ipv4().unwrap(); + let client_sockfd = + unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; + + net::connect_ipv4(client_sockfd, addr).unwrap(); + let (peerfd, _) = net::accept_ipv4(server_sockfd).unwrap(); + + libc_utils::write_all(peerfd, TEST_BYTES).unwrap(); + + let mut buffer = [0u8; TEST_BYTES.len()]; + let (buffer1, buffer2) = buffer.split_at_mut(2); + + let iov = [ + libc::iovec { iov_base: ptr::null_mut::(), iov_len: 0 as libc::size_t }, + libc::iovec { + iov_base: buffer1.as_mut_ptr().cast::(), + iov_len: buffer1.len() as libc::size_t, + }, + libc::iovec { + iov_base: buffer2.as_mut_ptr().cast::(), + iov_len: buffer2.len() as libc::size_t, + }, + ]; + + let num = unsafe { + errno_result(libc::readv(client_sockfd, iov.as_ptr(), iov.len() as libc::c_int)).unwrap() + }; + assert_eq!(num as usize, TEST_BYTES.len()); + // The vectored read should read the entire buffer because we don't have + // short reads on sockets. + assert_eq!(&buffer, TEST_BYTES); +} + +/// Test vectored writes with multiple buffers on a connected socket. +fn test_writev() { + let (server_sockfd, addr) = net::make_listener_ipv4().unwrap(); + let client_sockfd = + unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; + + net::connect_ipv4(client_sockfd, addr).unwrap(); + let (peerfd, _) = net::accept_ipv4(server_sockfd).unwrap(); + + let mut write_buffer = TEST_BYTES.to_owned(); + let (buffer1, buffer2) = write_buffer.split_at_mut(3); + + let iov = [ + libc::iovec { iov_base: ptr::null_mut::(), iov_len: 0 as libc::size_t }, + libc::iovec { + iov_base: buffer1.as_mut_ptr().cast::(), + iov_len: buffer1.len() as libc::size_t, + }, + libc::iovec { + iov_base: buffer2.as_mut_ptr().cast::(), + iov_len: buffer2.len() as libc::size_t, + }, + ]; + + let num = unsafe { + errno_result(libc::writev(client_sockfd, iov.as_ptr(), iov.len() as libc::c_int)).unwrap() + }; + assert_eq!(num as usize, TEST_BYTES.len()); + + let mut buffer = [0u8; TEST_BYTES.len()]; + libc_utils::read_exact(peerfd, &mut buffer).unwrap(); + // The vectored write should write the entire buffer because we don't have + // short writes on sockets. + assert_eq!(&buffer, TEST_BYTES); +} + /// Test the `getsockname` syscall on an IPv4 socket which is bound. /// The `getsockname` syscall should return the same address as to /// which the socket was bound to. @@ -425,10 +520,8 @@ fn test_getsockname_ipv4_connect() { let client_sockfd = unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; - // Spawn the server thread. - let server_thread = thread::spawn(move || net::accept_ipv4(server_sockfd).unwrap()); - net::connect_ipv4(client_sockfd, addr).unwrap(); + net::accept_ipv4(server_sockfd).unwrap(); let (_, sock_addr) = net::sockname_ipv4(|storage, len| unsafe { libc::getsockname(client_sockfd, storage, len) @@ -443,8 +536,6 @@ fn test_getsockname_ipv4_connect() { assert_eq!(addr.sin_family, sock_addr.sin_family); assert_ne!(addr.sin_addr.s_addr, sock_addr.sin_addr.s_addr); assert!(sock_addr.sin_port > 0); - - server_thread.join().unwrap(); } /// Test the `getsockname` syscall on an IPv6 socket which is bound. @@ -485,10 +576,8 @@ fn test_getpeername_ipv4() { let client_sockfd = unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; - // Spawn the server thread. - let server_thread = thread::spawn(move || net::accept_ipv4(server_sockfd).unwrap()); - net::connect_ipv4(client_sockfd, addr).unwrap(); + net::accept_ipv4(server_sockfd).unwrap(); let (_, peer_addr) = net::sockname_ipv4(|storage, len| unsafe { libc::getpeername(client_sockfd, storage, len) @@ -498,8 +587,6 @@ fn test_getpeername_ipv4() { assert_eq!(addr.sin_family, peer_addr.sin_family); assert_eq!(addr.sin_port, peer_addr.sin_port); assert_eq!(addr.sin_addr.s_addr, peer_addr.sin_addr.s_addr); - - server_thread.join().unwrap(); } /// Test the `getpeername` syscall on an IPv6 socket. @@ -510,10 +597,8 @@ fn test_getpeername_ipv6() { let client_sockfd = unsafe { errno_result(libc::socket(libc::AF_INET6, libc::SOCK_STREAM, 0)).unwrap() }; - // Spawn the server thread. - let server_thread = thread::spawn(move || net::accept_ipv6(server_sockfd).unwrap()); - net::connect_ipv6(client_sockfd, addr).unwrap(); + net::accept_ipv6(server_sockfd).unwrap(); let (_, peer_addr) = net::sockname_ipv6(|storage, len| unsafe { libc::getpeername(client_sockfd, storage, len) @@ -525,8 +610,6 @@ fn test_getpeername_ipv6() { assert_eq!(addr.sin6_flowinfo, peer_addr.sin6_flowinfo); assert_eq!(addr.sin6_scope_id, peer_addr.sin6_scope_id); assert_eq!(addr.sin6_addr.s6_addr, peer_addr.sin6_addr.s6_addr); - - server_thread.join().unwrap(); } /// Test shutting down TCP streams. diff --git a/src/tools/miri/tests/pass-dep/libc/libc-time.rs b/src/tools/miri/tests/pass-dep/libc/libc-time.rs index 141e0009101aa..f315d2ab117f9 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-time.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-time.rs @@ -66,7 +66,7 @@ fn test_posix_gettimeofday() { assert!(tv.tv_sec > 0); assert!(tv.tv_usec >= 0); // Theoretically this could be 0. - // Test that non-null tz returns an error. + // Test that non-null tz returns an error (because we don't support it). let mut tz = mem::MaybeUninit::::uninit(); let tz_ptr = tz.as_mut_ptr(); let is_error = unsafe { libc::gettimeofday(tp.as_mut_ptr(), tz_ptr.cast()) }; diff --git a/src/tools/miri/tests/pass/shims/fs.rs b/src/tools/miri/tests/pass/shims/fs.rs index e0da9f63876f1..b249c065318c2 100644 --- a/src/tools/miri/tests/pass/shims/fs.rs +++ b/src/tools/miri/tests/pass/shims/fs.rs @@ -2,13 +2,16 @@ #![feature(io_error_more)] #![feature(io_error_uncategorized)] +#![cfg_attr(unix, feature(unix_file_vectored_at))] use std::collections::BTreeMap; use std::ffi::OsString; use std::fs::{ self, File, OpenOptions, create_dir, read_dir, remove_dir, remove_dir_all, remove_file, rename, }; -use std::io::{Error, ErrorKind, IsTerminal, Read, Result, Seek, SeekFrom, Write}; +use std::io::{ + Error, ErrorKind, IoSlice, IoSliceMut, IsTerminal, Read, Result, Seek, SeekFrom, Write, +}; use std::path::Path; #[path = "../../utils/mod.rs"] @@ -39,6 +42,9 @@ fn main() { test_pread_pwrite(); #[cfg(not(any(target_os = "solaris", target_os = "android")))] test_flock(); + test_readv_writev(); + #[cfg(all(unix, not(any(target_os = "solaris", target_os = "android"))))] + test_preadv_pwritev(); } } @@ -400,7 +406,10 @@ fn test_pread_pwrite() { assert_eq!(&buf1, b" m"); } -// The standard library does not support this operation on Solaris, Android +// The standard library does not support this operation on Android +// (https://github.com/rust-lang/rust/issues/148325). +// Miri does not support the way this is implemented on Solaris +// (https://github.com/rust-lang/miri/issues/5038). #[cfg(not(any(target_os = "solaris", target_os = "android")))] fn test_flock() { let bytes = b"Hello, World!\n"; @@ -424,3 +433,82 @@ fn test_flock() { // Unlock exclusive lock file1.unlock().unwrap(); } + +/// Test vectored reads and vectored writes. +fn test_readv_writev() { + let bytes = b"hello world!"; + let path = utils::prepare_with_content("miri_test_fs_readv_writev.txt", bytes); + let mut f = OpenOptions::new().read(true).write(true).open(path).unwrap(); + + let mut read_buffer = [0u8; 10]; + let (buffer1, buffer2) = read_buffer.split_at_mut(5); + + let bytes_read = + f.read_vectored(&mut [IoSliceMut::new(buffer1), IoSliceMut::new(buffer2)]).unwrap(); + + // Vectored read should read at least a byte. + assert!(bytes_read > 0); + assert_eq!(read_buffer[0..bytes_read], bytes[0..bytes_read]); + + let write_buffer = b"some additional bytes"; + let (buffer1, buffer2) = write_buffer.split_at(write_buffer.len() / 2); + + let bytes_written = f.write_vectored(&[IoSlice::new(buffer1), IoSlice::new(buffer2)]).unwrap(); + + // Vectored write should write at least a byte. + assert!(bytes_written > 0); + + // Reset file cursor to read the written bytes. + f.seek(SeekFrom::Start(bytes_read as u64)).unwrap(); + let mut written_bytes = vec![0u8; bytes_written]; + f.read_exact(&mut written_bytes).unwrap(); + assert_eq!(written_bytes.as_slice(), &write_buffer[0..bytes_written]); +} + +/// Test vectored reads and vectored writes with byte offsets. +/// +/// **Note**: We skip this test on Solaris and Android targets. This is +/// because Solaris doesn't have `preadv`/`pwritev`, and on Android the +/// standard library uses `syscall(...)` for vectored reads/writes with +/// offsets because older Android versions also didn't have `preadv`/`pwritev`. +#[cfg(all(unix, not(any(target_os = "solaris", target_os = "android"))))] +fn test_preadv_pwritev() { + use std::os::unix::fs::FileExt; + + let bytes = b"hello world!"; + let path = utils::prepare_with_content("miri_test_fs_preadv_pwritev.txt", bytes); + let mut f = OpenOptions::new().read(true).write(true).open(path).unwrap(); + + const OFFSET: usize = 2; + + let mut read_buffer = [0u8; 10]; + let (buffer1, buffer2) = read_buffer.split_at_mut(5); + + let bytes_read = f + .read_vectored_at(&mut [IoSliceMut::new(buffer1), IoSliceMut::new(buffer2)], OFFSET as u64) + .unwrap(); + + // Vectored read should read at least a byte at the provided offset. + assert!(bytes_read > 0); + assert_eq!(read_buffer[0..bytes_read], bytes[OFFSET..(bytes_read + OFFSET)]); + + let write_buffer = b"some additional bytes"; + let (buffer1, buffer2) = write_buffer.split_at(write_buffer.len() / 2); + + let bytes_written = f + .write_vectored_at( + &[IoSlice::new(buffer1), IoSlice::new(buffer2)], + (bytes.len() + OFFSET) as u64, + ) + .unwrap(); + + // Vectored write should write at least a byte at the provided offset. + assert!(bytes_written > 0); + + // Reset file cursor to read the written bytes. We move the cursor + // to include the offset. + f.seek(SeekFrom::Start((bytes.len() + OFFSET) as u64)).unwrap(); + let mut written_bytes = vec![0u8; bytes_written]; + f.read_exact(&mut written_bytes).unwrap(); + assert_eq!(written_bytes.as_slice(), &write_buffer[0..bytes_written]); +} diff --git a/src/tools/miri/tests/pass/shims/socket.rs b/src/tools/miri/tests/pass/shims/socket.rs index 4f448fa44780b..335df02b54f82 100644 --- a/src/tools/miri/tests/pass/shims/socket.rs +++ b/src/tools/miri/tests/pass/shims/socket.rs @@ -16,6 +16,7 @@ fn main() { test_peer_addr(); test_shutdown(); test_sockopt_ttl(); + test_sockopt_nodelay(); } fn test_create_ipv4_listener() { @@ -26,21 +27,15 @@ fn test_create_ipv6_listener() { let _listener_ipv6 = TcpListener::bind("[::1]:0").unwrap(); } -/// Try to connect to a TCP listener running in a separate thread and -/// accepting connections. +/// Try to connect to a TCP listener and accepting connections. fn test_accept_and_connect() { let listener = TcpListener::bind("127.0.0.1:0").unwrap(); // Get local address with randomized port to know where // we need to connect to. let address = listener.local_addr().unwrap(); - let handle = thread::spawn(move || { - let (_stream, _addr) = listener.accept().unwrap(); - }); - let _stream = TcpStream::connect(address).unwrap(); - - handle.join().unwrap(); + let (_other_stream, _addr) = listener.accept().unwrap(); } /// Test reading and writing into two connected sockets and ensuring @@ -119,8 +114,6 @@ fn test_peer_addr() { /// Test shutting down TCP streams. fn test_shutdown() { let listener = TcpListener::bind("127.0.0.1:0").unwrap(); - // Get local address with randomized port to know where - // we need to connect to. let address = listener.local_addr().unwrap(); // Start server thread. @@ -157,7 +150,20 @@ fn test_shutdown() { /// Test setting and reading the TTL socket option. fn test_sockopt_ttl() { let listener = TcpListener::bind("127.0.0.1:0").unwrap(); - listener.ttl().unwrap(); + listener.set_ttl(16).unwrap(); + assert_eq!(listener.ttl().unwrap(), 16); +} + +/// Test setting and reading the TCP nodelay socket option. +fn test_sockopt_nodelay() { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let address = listener.local_addr().unwrap(); + + let stream = TcpStream::connect(address).unwrap(); + let _other_end = listener.accept().unwrap(); - // TODO: Once we support setting the TTL we should also test it here. + stream.set_nodelay(true).unwrap(); + assert_eq!(stream.nodelay().unwrap(), true); + stream.set_nodelay(false).unwrap(); + assert_eq!(stream.nodelay().unwrap(), false); } diff --git a/src/tools/miri/tests/pass/tree_borrows/cell-alternate-writes.tree.stderr b/src/tools/miri/tests/pass/tree_borrows/cell-alternate-writes.tree.stderr index e09aed2cf5d01..6edb1f91c90c2 100644 --- a/src/tools/miri/tests/pass/tree_borrows/cell-alternate-writes.tree.stderr +++ b/src/tools/miri/tests/pass/tree_borrows/cell-alternate-writes.tree.stderr @@ -1,7 +1,7 @@ ────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 -| Act | └─┬── +| Unq | └─┬── | ReIM| └─┬── |?Cel | ├──── |?Cel | └──── @@ -9,8 +9,8 @@ Warning: this tree is indicative only. Some tags may have been hidden. ────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 -| Act | └─┬── -| Act | └─┬── +| Unq | └─┬── +| Unq | └─┬── | Cel | ├──── | Cel | └──── ────────────────────────────────────────────────── diff --git a/src/tools/miri/tests/pass/tree_borrows/cell-alternate-writes.tree_implicit_writes.stderr b/src/tools/miri/tests/pass/tree_borrows/cell-alternate-writes.tree_implicit_writes.stderr index e09aed2cf5d01..6edb1f91c90c2 100644 --- a/src/tools/miri/tests/pass/tree_borrows/cell-alternate-writes.tree_implicit_writes.stderr +++ b/src/tools/miri/tests/pass/tree_borrows/cell-alternate-writes.tree_implicit_writes.stderr @@ -1,7 +1,7 @@ ────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 -| Act | └─┬── +| Unq | └─┬── | ReIM| └─┬── |?Cel | ├──── |?Cel | └──── @@ -9,8 +9,8 @@ Warning: this tree is indicative only. Some tags may have been hidden. ────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 -| Act | └─┬── -| Act | └─┬── +| Unq | └─┬── +| Unq | └─┬── | Cel | ├──── | Cel | └──── ────────────────────────────────────────────────── diff --git a/src/tools/miri/tests/pass/tree_borrows/cell-inside-box.tree.stderr b/src/tools/miri/tests/pass/tree_borrows/cell-inside-box.tree.stderr index 5dbfff718b1e6..3a7c55a8dab43 100644 --- a/src/tools/miri/tests/pass/tree_borrows/cell-inside-box.tree.stderr +++ b/src/tools/miri/tests/pass/tree_borrows/cell-inside-box.tree.stderr @@ -1,7 +1,7 @@ ────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 4 -| Act | └─┬── -| Act | └─┬── +| Unq | └─┬── +| Unq | └─┬── | ReIM| └──── ────────────────────────────────────────────────── diff --git a/src/tools/miri/tests/pass/tree_borrows/cell-inside-box.tree_implicit_writes.stderr b/src/tools/miri/tests/pass/tree_borrows/cell-inside-box.tree_implicit_writes.stderr index 5dbfff718b1e6..3a7c55a8dab43 100644 --- a/src/tools/miri/tests/pass/tree_borrows/cell-inside-box.tree_implicit_writes.stderr +++ b/src/tools/miri/tests/pass/tree_borrows/cell-inside-box.tree_implicit_writes.stderr @@ -1,7 +1,7 @@ ────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 4 -| Act | └─┬── -| Act | └─┬── +| Unq | └─┬── +| Unq | └─┬── | ReIM| └──── ────────────────────────────────────────────────── diff --git a/src/tools/miri/tests/pass/tree_borrows/cell-inside-struct.tree.stderr b/src/tools/miri/tests/pass/tree_borrows/cell-inside-struct.tree.stderr index 1d939329040fa..c7f7397593d33 100644 --- a/src/tools/miri/tests/pass/tree_borrows/cell-inside-struct.tree.stderr +++ b/src/tools/miri/tests/pass/tree_borrows/cell-inside-struct.tree.stderr @@ -1,6 +1,6 @@ ────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 8 -| Act | └─┬── +| Unq | └─┬── |?Cel | └──── ────────────────────────────────────────────────── diff --git a/src/tools/miri/tests/pass/tree_borrows/cell-inside-struct.tree_implicit_writes.stderr b/src/tools/miri/tests/pass/tree_borrows/cell-inside-struct.tree_implicit_writes.stderr index 1d939329040fa..c7f7397593d33 100644 --- a/src/tools/miri/tests/pass/tree_borrows/cell-inside-struct.tree_implicit_writes.stderr +++ b/src/tools/miri/tests/pass/tree_borrows/cell-inside-struct.tree_implicit_writes.stderr @@ -1,6 +1,6 @@ ────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 8 -| Act | └─┬── +| Unq | └─┬── |?Cel | └──── ────────────────────────────────────────────────── diff --git a/src/tools/miri/tests/pass/tree_borrows/end-of-protector.tree.stderr b/src/tools/miri/tests/pass/tree_borrows/end-of-protector.tree.stderr index 4d77d96776d31..74abcef5870b1 100644 --- a/src/tools/miri/tests/pass/tree_borrows/end-of-protector.tree.stderr +++ b/src/tools/miri/tests/pass/tree_borrows/end-of-protector.tree.stderr @@ -1,14 +1,14 @@ ────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 -| Act | └─┬── +| Unq | └─┬── | Res | └─┬── | Res | └──── ────────────────────────────────────────────────── ────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 -| Act | └─┬── +| Unq | └─┬── | Res | └─┬── | Res | └─┬── | Res | └─┬── @@ -17,7 +17,7 @@ Warning: this tree is indicative only. Some tags may have been hidden. ────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 -| Act | └─┬── +| Unq | └─┬── | Res | └─┬── | Res | ├─┬── | Res | │ └─┬── @@ -27,10 +27,10 @@ Warning: this tree is indicative only. Some tags may have been hidden. ────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 -| Act | └─┬── -| Act | └─┬── +| Unq | └─┬── +| Unq | └─┬── | Dis | ├─┬── | Dis | │ └─┬── | Dis | │ └──── -| Act | └──── +| Unq | └──── ────────────────────────────────────────────────── diff --git a/src/tools/miri/tests/pass/tree_borrows/end-of-protector.tree_implicit_writes.stderr b/src/tools/miri/tests/pass/tree_borrows/end-of-protector.tree_implicit_writes.stderr index d33bfcdc0097a..7ab96e3f378d9 100644 --- a/src/tools/miri/tests/pass/tree_borrows/end-of-protector.tree_implicit_writes.stderr +++ b/src/tools/miri/tests/pass/tree_borrows/end-of-protector.tree_implicit_writes.stderr @@ -1,24 +1,24 @@ ────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 -| Act | └─┬── +| Unq | └─┬── | Res | └─┬── | Res | └──── ────────────────────────────────────────────────── ────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 -| Act | └─┬── -| Act | └─┬── -| Act | └─┬── -| Act | └─┬── -| Act | └──── Strongly protected +| Unq | └─┬── +| Unq | └─┬── +| Unq | └─┬── +| Unq | └─┬── +| Unq | └──── Strongly protected ────────────────────────────────────────────────── ────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 -| Act | └─┬── -| Act | └─┬── +| Unq | └─┬── +| Unq | └─┬── | Frz | ├─┬── | Frz | │ └─┬── | Frz | │ └──── @@ -27,10 +27,10 @@ Warning: this tree is indicative only. Some tags may have been hidden. ────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 -| Act | └─┬── -| Act | └─┬── +| Unq | └─┬── +| Unq | └─┬── | Dis | ├─┬── | Dis | │ └─┬── | Dis | │ └──── -| Act | └──── +| Unq | └──── ────────────────────────────────────────────────── diff --git a/src/tools/miri/tests/pass/tree_borrows/formatting.stderr b/src/tools/miri/tests/pass/tree_borrows/formatting.stderr index 29f99034bab5c..5e4cc7be56bc0 100644 --- a/src/tools/miri/tests/pass/tree_borrows/formatting.stderr +++ b/src/tools/miri/tests/pass/tree_borrows/formatting.stderr @@ -1,17 +1,17 @@ ────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1.. 2.. 10.. 11.. 100.. 101..1000..1001..1024 -| Act | Act | Act | Act | Act | Act | Act | Act | Act | └─┬── -| Res | Act | Res | Act | Res | Act | Res | Act | Res | └─┬── -|-----| Act |-----|?Dis |-----|?Dis |-----|?Dis |-----| ├──── -|-----|-----|-----| Act |-----|?Dis |-----|?Dis |-----| ├──── +| Unq | Unq | Unq | Unq | Unq | Unq | Unq | Unq | Unq | └─┬── +| Res | Unq | Res | Unq | Res | Unq | Res | Unq | Res | └─┬── +|-----| Unq |-----|?Dis |-----|?Dis |-----|?Dis |-----| ├──── +|-----|-----|-----| Unq |-----|?Dis |-----|?Dis |-----| ├──── |-----|-----|-----|-----|-----| Frz |-----|?Dis |-----| ├──── -|-----|-----|-----|-----|-----|-----|-----| Act |-----| └──── +|-----|-----|-----|-----|-----|-----|-----| Unq |-----| └──── ────────────────────────────────────────────────── ────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 -| Act | └─┬── +| Unq | └─┬── | Frz | └─┬── | Frz | ├─┬── | Frz | │ ├──── diff --git a/src/tools/miri/tests/pass/tree_borrows/implicit-writes-permissions.stderr b/src/tools/miri/tests/pass/tree_borrows/implicit-writes-permissions.stderr index 1dd871530caad..faccba6b0eead 100644 --- a/src/tools/miri/tests/pass/tree_borrows/implicit-writes-permissions.stderr +++ b/src/tools/miri/tests/pass/tree_borrows/implicit-writes-permissions.stderr @@ -1,7 +1,7 @@ ────────────────────────────────────────────── 0.. 1 -| Act | └─┬── -| Act | └─┬── -| Act | └─┬── -| Act | └──── Strongly protected +| Unq | └─┬── +| Unq | └─┬── +| Unq | └─┬── +| Unq | └──── Strongly protected ────────────────────────────────────────────── diff --git a/src/tools/miri/tests/pass/tree_borrows/reborrow-is-read.tree.stderr b/src/tools/miri/tests/pass/tree_borrows/reborrow-is-read.tree.stderr index d589a06211182..a1f485208dfa9 100644 --- a/src/tools/miri/tests/pass/tree_borrows/reborrow-is-read.tree.stderr +++ b/src/tools/miri/tests/pass/tree_borrows/reborrow-is-read.tree.stderr @@ -1,15 +1,15 @@ ────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 -| Act | └─┬── -| Act | └─┬── -| Act | └──── +| Unq | └─┬── +| Unq | └─┬── +| Unq | └──── ────────────────────────────────────────────────── ────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 -| Act | └─┬── -| Act | └─┬── +| Unq | └─┬── +| Unq | └─┬── | Frz | ├──── | Res | └──── ────────────────────────────────────────────────── diff --git a/src/tools/miri/tests/pass/tree_borrows/reborrow-is-read.tree_implicit_writes.stderr b/src/tools/miri/tests/pass/tree_borrows/reborrow-is-read.tree_implicit_writes.stderr index d589a06211182..a1f485208dfa9 100644 --- a/src/tools/miri/tests/pass/tree_borrows/reborrow-is-read.tree_implicit_writes.stderr +++ b/src/tools/miri/tests/pass/tree_borrows/reborrow-is-read.tree_implicit_writes.stderr @@ -1,15 +1,15 @@ ────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 -| Act | └─┬── -| Act | └─┬── -| Act | └──── +| Unq | └─┬── +| Unq | └─┬── +| Unq | └──── ────────────────────────────────────────────────── ────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 -| Act | └─┬── -| Act | └─┬── +| Unq | └─┬── +| Unq | └─┬── | Frz | ├──── | Res | └──── ────────────────────────────────────────────────── diff --git a/src/tools/miri/tests/pass/tree_borrows/reserved.stderr b/src/tools/miri/tests/pass/tree_borrows/reserved.stderr index be90382640b4a..ab173919abf5a 100644 --- a/src/tools/miri/tests/pass/tree_borrows/reserved.stderr +++ b/src/tools/miri/tests/pass/tree_borrows/reserved.stderr @@ -2,7 +2,7 @@ ────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 -| Act | └─┬── +| Unq | └─┬── | ReIM| └─┬── | ReIM| ├─┬── | ReIM| │ └─┬── @@ -13,7 +13,7 @@ Warning: this tree is indicative only. Some tags may have been hidden. ────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 8 -| Act | └─┬── +| Unq | └─┬── | ReIM| └─┬── | ReIM| ├──── | ReIM| └──── @@ -22,16 +22,16 @@ Warning: this tree is indicative only. Some tags may have been hidden. ────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 8 -| Act | └─┬── -| Act | └─┬── +| Unq | └─┬── +| Unq | └─┬── | ReIM| ├──── -| Act | └──── +| Unq | └──── ────────────────────────────────────────────────── [protected] Foreign Read: Res -> Frz ────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 -| Act | └─┬── +| Unq | └─┬── | Res | └─┬── | Res | ├─┬── | Res | │ └─┬── @@ -42,7 +42,7 @@ Warning: this tree is indicative only. Some tags may have been hidden. ────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 -| Act | └─┬── +| Unq | └─┬── | Res | └─┬── | Res | ├──── | Res | └──── @@ -51,8 +51,8 @@ Warning: this tree is indicative only. Some tags may have been hidden. ────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 -| Act | └─┬── -| Act | └─┬── +| Unq | └─┬── +| Unq | └─┬── | Dis | ├──── -| Act | └──── +| Unq | └──── ────────────────────────────────────────────────── diff --git a/src/tools/miri/tests/pass/tree_borrows/wildcard/formatting.stderr b/src/tools/miri/tests/pass/tree_borrows/wildcard/formatting.stderr index 583c84534395c..d671f31d5a802 100644 --- a/src/tools/miri/tests/pass/tree_borrows/wildcard/formatting.stderr +++ b/src/tools/miri/tests/pass/tree_borrows/wildcard/formatting.stderr @@ -1,7 +1,7 @@ ────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 -| Act | └─┬── +| Unq | └─┬── | Frz | └─┬── | Frz | ├──── | Frz | └──── (exposed) diff --git a/src/tools/miri/tests/ui.rs b/src/tools/miri/tests/ui.rs index 96c0ccb7d6974..1449013e0478e 100644 --- a/src/tools/miri/tests/ui.rs +++ b/src/tools/miri/tests/ui.rs @@ -1,5 +1,4 @@ use std::env; -use std::ffi::OsString; use std::num::NonZero; use std::path::{Path, PathBuf}; use std::process::Command; @@ -330,13 +329,9 @@ fn main() -> Result<()> { let target = get_target(&host); let tmpdir = tempfile::Builder::new().prefix("miri-uitest-").tempdir()?; - let mut args = std::env::args_os(); - - // Skip the program name and check whether this is a `./miri run-dep` invocation - if let Some(first) = args.nth(1) - && first == "--miri-run-dep-mode" - { - return run_dep_mode(target, args); + // Check whether this is a `./miri run` invocation + if let Ok(mode) = env::var("MIRI_RUN_MODE") { + return run_mode(target, mode); } ui(Mode::Pass, "tests/pass", &target, WithoutDependencies, tmpdir.path())?; @@ -366,21 +361,67 @@ fn main() -> Result<()> { Ok(()) } -fn run_dep_mode(target: String, args: impl Iterator) -> Result<()> { +fn run_mode(target: String, mode: String) -> Result<()> { + let native = mode == "native"; + let mut config = miri_config(&target, "", Mode::RunDep, Some(WithDependencies { bless: false })); config.comment_defaults.base().custom.remove("edition"); // `./miri` adds an `--edition` in `args`, so don't set it twice + if native { + // Patch things up so that we actually compile the program. + config.program.envs.push(("MIRI_BE_RUSTC".into(), Some("host".into()))); + config.comment_defaults.base().set_custom( + "dependencies", + DependencyBuilder { + crate_manifest_path: Path::new("tests/deps").join("Cargo.toml"), + ..Default::default() + }, + ); + } config.fill_host_and_target()?; - let dep_builder = BuildManager::one_off(config.clone()); - // Only set these for the actual run, not the dep builder, so invalid flags do not fail - // the dependency build. - config.program.args = args.collect(); - let test_config = TestConfig::one_off_runner(config, PathBuf::new()); + // Reset `args` (otherwise we'll get JSON output). + config.program.args = vec![]; + // Compute the actual Miri invocation command. + let test_config = TestConfig::one_off_runner(config.clone(), PathBuf::new()); let mut cmd = test_config.config.program.build(&test_config.config.out_dir); - cmd.arg("--target").arg(test_config.config.target.as_ref().unwrap()); - // Build dependencies - test_config.apply_custom(&mut cmd, &dep_builder).expect("failed to build dependencies"); + // For some reason we need to set the target ourselves. + cmd.arg("--target").arg(&target); + // Also forward arguments to the program (skipping the binary name). + // We don't put this in the `config` since we don't want it to affect the dependency build. + cmd.args({ + let mut args = env::args_os(); + args.next().unwrap(); + args + }); + + // Build dependencies (which will mutate that command) + test_config + .apply_custom(&mut cmd, &BuildManager::one_off(config.clone())) + .expect("failed to build dependencies"); + // Finally, actually run Miri. + let exit_status = cmd.spawn()?.wait()?; + if !exit_status.success() { + std::process::exit(1) + } - if cmd.spawn()?.wait()?.success() { Ok(()) } else { std::process::exit(1) } + if native { + // We just built the program, we still have to run it. We can't use the ui_test `Run` flag + // as that needs an actual BuildManager, not just the one-off stub we have here. So we + // implement the core logic ourselves. + + // First, figure out the output binary by re-running the compiler with `--print`. + cmd.arg("--print").arg("file-names"); + let output = cmd.output()?; + let exe = std::str::from_utf8(&output.stdout).unwrap().trim(); + let exe = config.out_dir.join(exe); + // Then run that binary. + let mut cmd = Command::new(exe); + let exit_status = cmd.spawn()?.wait()?; + if !exit_status.success() { + std::process::exit(1) + } + } + + Ok(()) } diff --git a/src/tools/miri/tests/utils/miri_extern.rs b/src/tools/miri/tests/utils/miri_extern.rs index e9cde20412f49..3cc4610c6b0a3 100644 --- a/src/tools/miri/tests/utils/miri_extern.rs +++ b/src/tools/miri/tests/utils/miri_extern.rs @@ -172,3 +172,38 @@ extern "Rust" { /// As far as Miri is concerned, this is equivalent to `yield_now`. pub fn miri_spin_loop(); } + +// Stubs so we can run things without Miri. +#[cfg(not(miri))] +mod stubs { + use std::ffi::CStr; + use std::io::Write; + + pub unsafe fn miri_write_to_stdout(bytes: &[u8]) { + std::io::stdout().write_all(bytes).unwrap() + } + + pub unsafe fn miri_write_to_stderr(bytes: &[u8]) { + std::io::stderr().write_all(bytes).unwrap() + } + + pub unsafe fn miri_host_to_target_path( + path: *const core::ffi::c_char, + out: *mut core::ffi::c_char, + out_size: usize, + ) -> usize { + let path = CStr::from_ptr(path); + let len = path.count_bytes() + 1; + if out_size >= len { + out.copy_from(path.as_ptr(), len); + 0 + } else { + len + } + } + + pub unsafe fn miri_run_provenance_gc() {} +} + +#[cfg(not(miri))] +pub use stubs::*; diff --git a/tests/mir-opt/build_correct_coerce.rs b/tests/mir-opt/build_correct_coerce.rs index 004e1309b0b26..0721a1fddf067 100644 --- a/tests/mir-opt/build_correct_coerce.rs +++ b/tests/mir-opt/build_correct_coerce.rs @@ -1,5 +1,3 @@ -//@ skip-filecheck - // Validate that we record the target for the `as` coercion as `for<'a> fn(&'a (), &'a ())`, // and not `for<'a, 'b>(&'a (), &'b ())`. We previously did the latter due to a bug in // the code that records adjustments in HIR typeck. @@ -8,5 +6,8 @@ fn foo<'a, 'b>(_: &'a (), _: &'b ()) {} // EMIT_MIR build_correct_coerce.main.built.after.mir fn main() { + // CHECK-LABEL: fn main( + // CHECK: let _1: for<'a> fn(&'a (), &'a ()); + // CHECK: _1 = foo as for<'a> fn(&'a (), &'a ()) let x = foo as for<'a> fn(&'a (), &'a ()); } diff --git a/tests/ui/issues/issue-18188.rs b/tests/ui/closures/boxed-closure-captures-fnmut-with-ref.rs similarity index 88% rename from tests/ui/issues/issue-18188.rs rename to tests/ui/closures/boxed-closure-captures-fnmut-with-ref.rs index b3b008229a53a..6f2c38843d754 100644 --- a/tests/ui/issues/issue-18188.rs +++ b/tests/ui/closures/boxed-closure-captures-fnmut-with-ref.rs @@ -1,3 +1,5 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/18188 + //@ check-pass pub trait Promisable: Send + Sync {} diff --git a/tests/ui/issues/issue-3609.rs b/tests/ui/closures/boxed-closure-sent-through-thread.rs similarity index 90% rename from tests/ui/issues/issue-3609.rs rename to tests/ui/closures/boxed-closure-sent-through-thread.rs index a226e5b01362a..14320e0c4ab3c 100644 --- a/tests/ui/issues/issue-3609.rs +++ b/tests/ui/closures/boxed-closure-sent-through-thread.rs @@ -1,3 +1,5 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/3609 + //@ check-pass #![allow(unused_must_use)] #![allow(dead_code)] diff --git a/tests/ui/issues/issue-3424.rs b/tests/ui/closures/boxed-closure-with-borrowed-param.rs similarity index 84% rename from tests/ui/issues/issue-3424.rs rename to tests/ui/closures/boxed-closure-with-borrowed-param.rs index 4d1a652142032..2d8dd4d800b85 100644 --- a/tests/ui/issues/issue-3424.rs +++ b/tests/ui/closures/boxed-closure-with-borrowed-param.rs @@ -1,3 +1,5 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/3424 + //@ check-pass #![allow(dead_code)] #![allow(non_camel_case_types)] diff --git a/tests/ui/issues/issue-20575.rs b/tests/ui/closures/call-boxed-closure-no-args.rs similarity index 76% rename from tests/ui/issues/issue-20575.rs rename to tests/ui/closures/call-boxed-closure-no-args.rs index b213b79d37cab..bf6b73ee6fd57 100644 --- a/tests/ui/issues/issue-20575.rs +++ b/tests/ui/closures/call-boxed-closure-no-args.rs @@ -1,3 +1,5 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/20575 + //@ run-pass // Test that overloaded calls work with zero arity closures diff --git a/tests/ui/issues/issue-17816.rs b/tests/ui/closures/call-closure-through-lifetime-generic-struct.rs similarity index 81% rename from tests/ui/issues/issue-17816.rs rename to tests/ui/closures/call-closure-through-lifetime-generic-struct.rs index da28a14685f6c..aa2180e3c7f09 100644 --- a/tests/ui/issues/issue-17816.rs +++ b/tests/ui/closures/call-closure-through-lifetime-generic-struct.rs @@ -1,3 +1,5 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/17816 + //@ run-pass #![allow(unused_variables)] use std::marker::PhantomData; diff --git a/tests/ui/issues/issue-32389.rs b/tests/ui/closures/call-fnmut-trait-object-via-ref-mut.rs similarity index 69% rename from tests/ui/issues/issue-32389.rs rename to tests/ui/closures/call-fnmut-trait-object-via-ref-mut.rs index 683c4874e8c20..2f1d29f77f27c 100644 --- a/tests/ui/issues/issue-32389.rs +++ b/tests/ui/closures/call-fnmut-trait-object-via-ref-mut.rs @@ -1,3 +1,5 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/32389 + //@ run-pass fn foo() -> T { loop {} } diff --git a/tests/ui/issues/issue-34349.rs b/tests/ui/closures/closure-kind-caching-during-upvar-inference.rs similarity index 91% rename from tests/ui/issues/issue-34349.rs rename to tests/ui/closures/closure-kind-caching-during-upvar-inference.rs index d861802610aac..e24e23d49d555 100644 --- a/tests/ui/issues/issue-34349.rs +++ b/tests/ui/closures/closure-kind-caching-during-upvar-inference.rs @@ -1,3 +1,5 @@ +//! Issue: https://github.com/rust-lang/rust/issues/34349 + // This is a regression test for a problem encountered around upvar // inference and trait caching: in particular, we were entering a // temporary closure kind during inference, and then caching results diff --git a/tests/ui/issues/issue-34349.stderr b/tests/ui/closures/closure-kind-caching-during-upvar-inference.stderr similarity index 85% rename from tests/ui/issues/issue-34349.stderr rename to tests/ui/closures/closure-kind-caching-during-upvar-inference.stderr index 6a6188f10c8f8..291ad5960bc21 100644 --- a/tests/ui/issues/issue-34349.stderr +++ b/tests/ui/closures/closure-kind-caching-during-upvar-inference.stderr @@ -1,5 +1,5 @@ error[E0525]: expected a closure that implements the `Fn` trait, but this closure only implements `FnMut` - --> $DIR/issue-34349.rs:16:17 + --> $DIR/closure-kind-caching-during-upvar-inference.rs:18:17 | LL | let diary = || { | ^^ this closure implements `FnMut`, not `Fn` @@ -12,7 +12,7 @@ LL | apply(diary); | required by a bound introduced by this call | note: required by a bound in `apply` - --> $DIR/issue-34349.rs:11:32 + --> $DIR/closure-kind-caching-during-upvar-inference.rs:13:32 | LL | fn apply(f: F) where F: Fn() { | ^^^^ required by this bound in `apply` diff --git a/tests/ui/issues/issue-40000.rs b/tests/ui/closures/closure-to-fn-pointer-lifetime-error.rs similarity index 75% rename from tests/ui/issues/issue-40000.rs rename to tests/ui/closures/closure-to-fn-pointer-lifetime-error.rs index a6e05e7ba02a3..74d5470f89665 100644 --- a/tests/ui/issues/issue-40000.rs +++ b/tests/ui/closures/closure-to-fn-pointer-lifetime-error.rs @@ -1,3 +1,5 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/40000 + fn main() { let bar: fn(&mut u32) = |_| {}; diff --git a/tests/ui/issues/issue-40000.stderr b/tests/ui/closures/closure-to-fn-pointer-lifetime-error.stderr similarity index 85% rename from tests/ui/issues/issue-40000.stderr rename to tests/ui/closures/closure-to-fn-pointer-lifetime-error.stderr index 0737a9610e294..7466751e43b51 100644 --- a/tests/ui/issues/issue-40000.stderr +++ b/tests/ui/closures/closure-to-fn-pointer-lifetime-error.stderr @@ -1,5 +1,5 @@ error[E0308]: mismatched types - --> $DIR/issue-40000.rs:6:9 + --> $DIR/closure-to-fn-pointer-lifetime-error.rs:8:9 | LL | foo(bar); | ^^^ one type is more general than the other @@ -8,7 +8,7 @@ LL | foo(bar); found trait object `dyn Fn(&i32)` error[E0308]: mismatched types - --> $DIR/issue-40000.rs:6:9 + --> $DIR/closure-to-fn-pointer-lifetime-error.rs:8:9 | LL | foo(bar); | ^^^ one type is more general than the other diff --git a/tests/ui/issues/issue-28181.rs b/tests/ui/closures/closure-with-fixed-size-array-param.rs similarity index 62% rename from tests/ui/issues/issue-28181.rs rename to tests/ui/closures/closure-with-fixed-size-array-param.rs index e1cb5ba1c8828..f12df2e5f628a 100644 --- a/tests/ui/issues/issue-28181.rs +++ b/tests/ui/closures/closure-with-fixed-size-array-param.rs @@ -1,3 +1,5 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/28181 + //@ run-pass fn bar(f: F) -> usize where F: Fn([usize; 1]) -> usize { f([2]) } diff --git a/tests/ui/issues/issue-26484.rs b/tests/ui/closures/debug-info-for-closure-capture.rs similarity index 68% rename from tests/ui/issues/issue-26484.rs rename to tests/ui/closures/debug-info-for-closure-capture.rs index c7053505567ab..2a8db27bda805 100644 --- a/tests/ui/issues/issue-26484.rs +++ b/tests/ui/closures/debug-info-for-closure-capture.rs @@ -1,3 +1,5 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/26484 + //@ run-pass //@ compile-flags:-g diff --git a/tests/ui/issues/issue-20174.rs b/tests/ui/closures/destructure-newtype-closure.rs similarity index 68% rename from tests/ui/issues/issue-20174.rs rename to tests/ui/closures/destructure-newtype-closure.rs index 7b49fe8c7b472..f8543e64cb353 100644 --- a/tests/ui/issues/issue-20174.rs +++ b/tests/ui/closures/destructure-newtype-closure.rs @@ -1,3 +1,5 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/20174 + //@ run-pass struct GradFn usize>(F); diff --git a/tests/ui/issues/issue-36260.rs b/tests/ui/closures/drop-glue-for-closure-with-captures.rs similarity index 81% rename from tests/ui/issues/issue-36260.rs rename to tests/ui/closures/drop-glue-for-closure-with-captures.rs index 265b0d2f80217..2ccf4c31a995e 100644 --- a/tests/ui/issues/issue-36260.rs +++ b/tests/ui/closures/drop-glue-for-closure-with-captures.rs @@ -1,3 +1,5 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/36260 + //@ run-pass // Make sure this compiles without getting a linker error because of missing // drop-glue because the collector missed adding drop-glue for the closure: diff --git a/tests/ui/issues/issue-22346.rs b/tests/ui/closures/explicit-return-in-closure-and-outer-fn.rs similarity index 77% rename from tests/ui/issues/issue-22346.rs rename to tests/ui/closures/explicit-return-in-closure-and-outer-fn.rs index 710dc0acda7e9..74e0ea345dd62 100644 --- a/tests/ui/issues/issue-22346.rs +++ b/tests/ui/closures/explicit-return-in-closure-and-outer-fn.rs @@ -1,3 +1,5 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/22346 + //@ run-pass #![allow(dead_code)] diff --git a/tests/ui/issues/issue-17897.rs b/tests/ui/closures/fn-sugar-in-boxed-trait-object.rs similarity index 68% rename from tests/ui/issues/issue-17897.rs rename to tests/ui/closures/fn-sugar-in-boxed-trait-object.rs index dbb560a199bf7..8f475057f376d 100644 --- a/tests/ui/issues/issue-17897.rs +++ b/tests/ui/closures/fn-sugar-in-boxed-trait-object.rs @@ -1,3 +1,5 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/17897 + //@ run-pass fn action(mut cb: Box usize>) -> usize { cb(1) diff --git a/tests/ui/issues/issue-23046.rs b/tests/ui/closures/hrtb-closure-in-recursive-enum-type-error.rs similarity index 88% rename from tests/ui/issues/issue-23046.rs rename to tests/ui/closures/hrtb-closure-in-recursive-enum-type-error.rs index a68369616d8b6..e66603a1f7dc6 100644 --- a/tests/ui/issues/issue-23046.rs +++ b/tests/ui/closures/hrtb-closure-in-recursive-enum-type-error.rs @@ -1,3 +1,5 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/23046 + pub enum Expr<'var, VAR> { Let(Box>, Box Fn(Expr<'v, VAR>) -> Expr<'v, VAR> + 'var>) diff --git a/tests/ui/issues/issue-23046.stderr b/tests/ui/closures/hrtb-closure-in-recursive-enum-type-error.stderr similarity index 87% rename from tests/ui/issues/issue-23046.stderr rename to tests/ui/closures/hrtb-closure-in-recursive-enum-type-error.stderr index f70ac0c9f388a..56ebb0a339a50 100644 --- a/tests/ui/issues/issue-23046.stderr +++ b/tests/ui/closures/hrtb-closure-in-recursive-enum-type-error.stderr @@ -1,5 +1,5 @@ error[E0282]: type annotations needed for `Expr<'_, _>` - --> $DIR/issue-23046.rs:17:15 + --> $DIR/hrtb-closure-in-recursive-enum-type-error.rs:19:15 | LL | let ex = |x| { | ^ diff --git a/tests/ui/issues/issue-29522.rs b/tests/ui/closures/module-path-not-confused-with-upvar.rs similarity index 80% rename from tests/ui/issues/issue-29522.rs rename to tests/ui/closures/module-path-not-confused-with-upvar.rs index 2a39ef28bdbbc..9eb56bee2f33c 100644 --- a/tests/ui/issues/issue-29522.rs +++ b/tests/ui/closures/module-path-not-confused-with-upvar.rs @@ -1,3 +1,5 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/29522 + //@ run-pass #![allow(unused_variables)] // check that we don't accidentally capture upvars just because their name diff --git a/tests/ui/issues/issue-16668.rs b/tests/ui/closures/move-closure-can-call-captured-fnmut.rs similarity index 87% rename from tests/ui/issues/issue-16668.rs rename to tests/ui/closures/move-closure-can-call-captured-fnmut.rs index 2b2370b0e2b4c..8267dd513417a 100644 --- a/tests/ui/issues/issue-16668.rs +++ b/tests/ui/closures/move-closure-can-call-captured-fnmut.rs @@ -1,3 +1,5 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/16668 + //@ check-pass #![allow(dead_code)] struct Parser<'a, I, O> { diff --git a/tests/ui/issues/issue-16560.rs b/tests/ui/closures/move-closure-is-send.rs similarity index 82% rename from tests/ui/issues/issue-16560.rs rename to tests/ui/closures/move-closure-is-send.rs index d9a7aa9101d3f..ace163a37d839 100644 --- a/tests/ui/issues/issue-16560.rs +++ b/tests/ui/closures/move-closure-is-send.rs @@ -1,3 +1,5 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/16560 + //@ run-pass #![allow(unused_variables)] //@ needs-threads diff --git a/tests/ui/issues/issue-16671.rs b/tests/ui/closures/no-unused-mut-warning-for-move-closure.rs similarity index 68% rename from tests/ui/issues/issue-16671.rs rename to tests/ui/closures/no-unused-mut-warning-for-move-closure.rs index f7f4f4348afab..a8c8aae3bb299 100644 --- a/tests/ui/issues/issue-16671.rs +++ b/tests/ui/closures/no-unused-mut-warning-for-move-closure.rs @@ -1,3 +1,5 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/16671 + //@ run-pass #![deny(warnings)] diff --git a/tests/ui/issues/issue-19127.rs b/tests/ui/closures/pass-lifetime-fn-to-generic-fnonce.rs similarity index 68% rename from tests/ui/issues/issue-19127.rs rename to tests/ui/closures/pass-lifetime-fn-to-generic-fnonce.rs index 2172c631b841d..2da3ac24eb5b0 100644 --- a/tests/ui/issues/issue-19127.rs +++ b/tests/ui/closures/pass-lifetime-fn-to-generic-fnonce.rs @@ -1,3 +1,5 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/19127 + //@ run-pass #![allow(unused_variables)] diff --git a/tests/ui/issues/issue-16994.rs b/tests/ui/closures/ref-binding-lifetime-in-closure-pattern.rs similarity index 72% rename from tests/ui/issues/issue-16994.rs rename to tests/ui/closures/ref-binding-lifetime-in-closure-pattern.rs index fa3988e099945..72648a92decf3 100644 --- a/tests/ui/issues/issue-16994.rs +++ b/tests/ui/closures/ref-binding-lifetime-in-closure-pattern.rs @@ -1,3 +1,5 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/16994 + //@ check-pass fn cb<'a,T>(_x: Box, bool))) -> T>) -> T { diff --git a/tests/ui/issues/issue-46069.rs b/tests/ui/closures/region-obligations-closure-and-projection.rs similarity index 86% rename from tests/ui/issues/issue-46069.rs rename to tests/ui/closures/region-obligations-closure-and-projection.rs index adfb567d7dd32..9010b7c8c6bed 100644 --- a/tests/ui/issues/issue-46069.rs +++ b/tests/ui/closures/region-obligations-closure-and-projection.rs @@ -1,3 +1,5 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/46069 + //@ run-pass use std::iter::{Fuse, Cloned}; use std::slice::Iter; diff --git a/tests/ui/issues/issue-24036.rs b/tests/ui/closures/unique-closure-type-mismatch.rs similarity index 80% rename from tests/ui/issues/issue-24036.rs rename to tests/ui/closures/unique-closure-type-mismatch.rs index 7df036c8e3a45..ee05c9ed77e40 100644 --- a/tests/ui/issues/issue-24036.rs +++ b/tests/ui/closures/unique-closure-type-mismatch.rs @@ -1,3 +1,5 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/24036 + fn closure_to_loc() { let mut x = |c| c + 1; x = |c| c + 1; diff --git a/tests/ui/issues/issue-24036.stderr b/tests/ui/closures/unique-closure-type-mismatch.stderr similarity index 75% rename from tests/ui/issues/issue-24036.stderr rename to tests/ui/closures/unique-closure-type-mismatch.stderr index 184383b736942..3a31b6db4f62b 100644 --- a/tests/ui/issues/issue-24036.stderr +++ b/tests/ui/closures/unique-closure-type-mismatch.stderr @@ -1,18 +1,18 @@ error[E0308]: mismatched types - --> $DIR/issue-24036.rs:3:9 + --> $DIR/unique-closure-type-mismatch.rs:5:9 | LL | let mut x = |c| c + 1; | --- the expected closure LL | x = |c| c + 1; | ^^^^^^^^^ expected closure, found a different closure | - = note: expected closure `{closure@$DIR/issue-24036.rs:2:17: 2:20}` - found closure `{closure@$DIR/issue-24036.rs:3:9: 3:12}` + = note: expected closure `{closure@$DIR/unique-closure-type-mismatch.rs:4:17: 4:20}` + found closure `{closure@$DIR/unique-closure-type-mismatch.rs:5:9: 5:12}` = note: no two closures, even if identical, have the same type = help: consider boxing your closure and/or using it as a trait object error[E0284]: type annotations needed - --> $DIR/issue-24036.rs:9:15 + --> $DIR/unique-closure-type-mismatch.rs:11:15 | LL | 1 => |c| c + 1, | ^ - type must be known at this point