diff --git a/src/tools/miri/Cargo.toml b/src/tools/miri/Cargo.toml index 3255885d95be..6aa93d60e9b6 100644 --- a/src/tools/miri/Cargo.toml +++ b/src/tools/miri/Cargo.toml @@ -1,5 +1,4 @@ [package] -authors = ["Miri Team"] description = "An experimental interpreter for Rust MIR (core driver)." license = "MIT OR Apache-2.0" name = "miri" diff --git a/src/tools/miri/bench-cargo-miri/mse/Cargo.toml b/src/tools/miri/bench-cargo-miri/mse/Cargo.toml index 7b4c2dc758fa..10f14636092b 100644 --- a/src/tools/miri/bench-cargo-miri/mse/Cargo.toml +++ b/src/tools/miri/bench-cargo-miri/mse/Cargo.toml @@ -1,7 +1,6 @@ [package] name = "mse" version = "0.1.0" -authors = ["Ralf Jung "] edition = "2018" [dependencies] diff --git a/src/tools/miri/bench-cargo-miri/serde1/Cargo.toml b/src/tools/miri/bench-cargo-miri/serde1/Cargo.toml index 7cb863a7abf3..66429f4974f9 100644 --- a/src/tools/miri/bench-cargo-miri/serde1/Cargo.toml +++ b/src/tools/miri/bench-cargo-miri/serde1/Cargo.toml @@ -1,7 +1,6 @@ [package] name = "cargo-miri-test" version = "0.1.0" -authors = ["Oliver Schneider "] edition = "2018" [dependencies] diff --git a/src/tools/miri/bench-cargo-miri/serde2/Cargo.toml b/src/tools/miri/bench-cargo-miri/serde2/Cargo.toml index 7cb863a7abf3..66429f4974f9 100644 --- a/src/tools/miri/bench-cargo-miri/serde2/Cargo.toml +++ b/src/tools/miri/bench-cargo-miri/serde2/Cargo.toml @@ -1,7 +1,6 @@ [package] name = "cargo-miri-test" version = "0.1.0" -authors = ["Oliver Schneider "] edition = "2018" [dependencies] diff --git a/src/tools/miri/cargo-miri/Cargo.toml b/src/tools/miri/cargo-miri/Cargo.toml index dd7e4c12d3a4..d240a5d665f1 100644 --- a/src/tools/miri/cargo-miri/Cargo.toml +++ b/src/tools/miri/cargo-miri/Cargo.toml @@ -1,5 +1,4 @@ [package] -authors = ["Miri Team"] description = "An experimental interpreter for Rust MIR (cargo wrapper)." license = "MIT OR Apache-2.0" name = "cargo-miri" diff --git a/src/tools/miri/genmc-sys/Cargo.toml b/src/tools/miri/genmc-sys/Cargo.toml index 37fcc58070a3..a67d1cc2e77f 100644 --- a/src/tools/miri/genmc-sys/Cargo.toml +++ b/src/tools/miri/genmc-sys/Cargo.toml @@ -1,5 +1,4 @@ [package] -authors = ["Miri Team"] license = "MIT OR Apache-2.0" name = "genmc-sys" version = "0.1.0" diff --git a/src/tools/miri/miri-script/Cargo.toml b/src/tools/miri/miri-script/Cargo.toml index 39858880e8c6..fd9a563e1faf 100644 --- a/src/tools/miri/miri-script/Cargo.toml +++ b/src/tools/miri/miri-script/Cargo.toml @@ -1,5 +1,4 @@ [package] -authors = ["Miri Team"] description = "Helpers for miri maintenance" license = "MIT OR Apache-2.0" name = "miri-script" diff --git a/src/tools/miri/miri-script/src/util.rs b/src/tools/miri/miri-script/src/util.rs index 6121096f8230..fd8b1958689d 100644 --- a/src/tools/miri/miri-script/src/util.rs +++ b/src/tools/miri/miri-script/src/util.rs @@ -157,7 +157,11 @@ pub fn install_to_sysroot( let features = features_to_args(features); // Install binaries to the miri toolchain's `sysroot` so they do not interact with other toolchains. // (Not using `cargo_cmd` as `install` is special and doesn't use `--manifest-path`.) - cmd!(self.sh, "{cargo_bin} +{toolchain} install {cargo_extra_flags...} --path {path} --force --root {sysroot} {features...} {args...}").run()?; + // Adding `--locked` so that behavior is closer to `./miri build`. However, cargo doesn't + // like `--locked --locked` so we need extra logic to avoid that. + let locked_flag = + if cargo_extra_flags.iter().any(|f| f == "--locked") { None } else { Some("--locked") }; + cmd!(self.sh, "{cargo_bin} +{toolchain} install {locked_flag...} {cargo_extra_flags...} --path {path} --force --root {sysroot} {features...} {args...}").run()?; Ok(()) } diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index a5106c872503..4bb951342451 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -44860d3e9ef700cac0b4a61d924f41f46bf1b447 +63b1dfc0e00fd6f8ad7cd8817fc712e7d9b7be59 diff --git a/src/tools/miri/src/concurrency/blocking_io.rs b/src/tools/miri/src/concurrency/blocking_io.rs index 35d7474347ab..9dc9bbfd1f1d 100644 --- a/src/tools/miri/src/concurrency/blocking_io.rs +++ b/src/tools/miri/src/concurrency/blocking_io.rs @@ -1,12 +1,15 @@ +use std::cell::RefMut; use std::collections::BTreeMap; use std::io; +use std::ops::BitOrAssign; use std::time::Duration; use mio::event::Source; use mio::{Events, Interest, Poll, Token}; -use rustc_data_structures::fx::FxHashMap; -use crate::shims::{FdId, FileDescriptionRef}; +use crate::shims::{ + EpollEvalContextExt, FdId, FileDescription, FileDescriptionRef, WeakFileDescriptionRef, +}; use crate::*; /// Capacity of the event queue which can be polled at a time. @@ -14,24 +17,113 @@ /// this value can be set rather low. const IO_EVENT_CAPACITY: usize = 16; -/// Trait for values that contain a mio [`Source`]. -pub trait WithSource { +/// Trait for file descriptions that contain a mio [`Source`]. +pub trait SourceFileDescription: FileDescription { /// Invoke `f` on the source inside `self`. fn with_source(&self, f: &mut dyn FnMut(&mut dyn Source) -> io::Result<()>) -> io::Result<()>; + + /// Get a mutable reference to the readiness of the source. + fn get_readiness_mut(&self) -> RefMut<'_, BlockingIoSourceReadiness>; } -/// An interest receiver defines the action that should be taken when -/// the associated [`Interest`] is fulfilled. -#[derive(Debug, Hash, PartialEq, Clone, Copy, Eq, PartialOrd, Ord)] -pub enum InterestReceiver { - /// The specified thread should be unblocked. - UnblockThread(ThreadId), +/// An I/O interest for a blocked thread. Note that all threads are always considered +/// to be interested in "error" events. +#[derive(Debug, Clone, Copy)] +pub enum BlockingIoInterest { + /// The blocked thread is interested in [`Interest::READABLE`]. + Read, + /// The blocked thread is interested in [`Interest::WRITABLE`]. + Write, + /// The blocked thread is interested in [`Interest::READABLE`] and + /// [`Interest::WRITABLE`]. + ReadWrite, +} + +/// Struct reflecting the readiness of a source file description. +#[derive(Debug)] +pub struct BlockingIoSourceReadiness { + /// Boolean whether the source is currently readable. + pub readable: bool, + /// Boolean whether the source is currently writable. + pub writable: bool, + /// Boolean whether the read end of the source has been + /// closed. + pub read_closed: bool, + /// Boolean whether the write end of the source has been + /// closed. + pub write_closed: bool, + /// Boolean whether the source currently has an error. + pub error: bool, +} + +impl BlockingIoSourceReadiness { + pub fn empty() -> Self { + Self { + readable: false, + writable: false, + read_closed: false, + write_closed: false, + error: false, + } + } + + /// Check whether the current readiness fulfills the blocking I/O interest of + /// `interest`. + /// This function also returns `true` if the error readiness is set + /// even when the requested interest might not be fulfilled. + fn fulfills_interest(&self, interest: &BlockingIoInterest) -> bool { + match interest { + BlockingIoInterest::Read => self.readable || self.error, + BlockingIoInterest::Write => self.writable || self.error, + BlockingIoInterest::ReadWrite => self.readable || self.writable || self.error, + } + } +} + +impl BitOrAssign for BlockingIoSourceReadiness { + fn bitor_assign(&mut self, rhs: Self) { + self.readable |= rhs.readable; + self.writable |= rhs.writable; + self.read_closed |= rhs.read_closed; + self.write_closed |= rhs.write_closed; + self.error |= rhs.error; + } +} + +impl From<&mio::event::Event> for BlockingIoSourceReadiness { + fn from(event: &mio::event::Event) -> Self { + Self { + readable: event.is_readable(), + writable: event.is_writable(), + read_closed: event.is_read_closed(), + write_closed: event.is_write_closed(), + error: event.is_error(), + } + } +} + +struct BlockingIoSource { + /// The source file description which is registered into the poll. + /// We only store weak references such that source file descriptions + /// can be destroyed whilst they are registered. However, they are required + /// to deregister themselves when [`FileDescription::destroy`] is called. + fd: WeakFileDescriptionRef, + /// The threads which are blocked on the I/O source, and the interest indicating + /// when they should be unblocked. + blocked_threads: BTreeMap, } /// Manager for managing blocking host I/O in a non-blocking manner. /// We use [`Poll`] to poll for new I/O events from the OS for sources /// registered using this manager. /// +/// The semantics of this manager are that host I/O sources are registered +/// to a [`Poll`] for their entire lifespan. Once host readiness events happen +/// on a registered source, its internal epoll readiness gets updated -- even +/// when the source isn't part of an active epoll instance. Also, for the entire +/// lifespan of the source, threads can be added which should be unblocked +/// once a certain [`BlockingIoSourceReadiness`] for an I/O source is satisfied. +/// /// Since blocking host I/O is inherently non-deterministic, no method on this /// manager should be called when isolation is enabled. The only exception is /// the [`BlockingIoManager::new`] function to create the manager. Everywhere else, @@ -44,10 +136,9 @@ pub struct BlockingIoManager { /// This is not part of the state and only stored to avoid allocating a /// new buffer for every poll. events: Events, - /// Map from source ids to the actual sources and their registered receivers - /// together with their associated interests. - sources: - BTreeMap, FxHashMap)>, + /// Map from source file description ids to the actual sources and their + /// blocked threads. + sources: BTreeMap, } impl BlockingIoManager { @@ -70,133 +161,176 @@ pub fn new(communicate: bool) -> Result { /// specified duration. /// - If the timeout is [`None`] the poll blocks indefinitely until an event occurs. /// - /// Returns the interest receivers for all file descriptions which received an I/O event together - /// with the file description they were registered for. - pub fn poll( - &mut self, + /// The events also immediately get processed: threads get unblocked, and epoll readiness gets updated. + pub fn poll<'tcx>( + ecx: &mut MiriInterpCx<'tcx>, timeout: Option, - ) -> Result)>, io::Error> { - let poll = - self.poll.as_mut().expect("Blocking I/O should not be called with isolation enabled"); + ) -> InterpResult<'tcx, Result<(), io::Error>> { + let poll = ecx + .machine + .blocking_io + .poll + .as_mut() + .expect("Blocking I/O should not be called with isolation enabled"); // Poll for new I/O events from OS and store them in the events buffer. - poll.poll(&mut self.events, timeout)?; + if let Err(err) = poll.poll(&mut ecx.machine.blocking_io.events, timeout) { + return interp_ok(Err(err)); + }; - let ready = self + let event_fds = ecx + .machine + .blocking_io .events .iter() - .flat_map(|event| { + .map(|event| { let token = event.token(); // We know all tokens are valid `FdId`. let fd_id = FdId::new_unchecked(token.0); - let (source, interests) = - self.sources.get(&fd_id).expect("Source should be registered"); - assert_eq!(source.id(), fd_id); - // Because we allow spurious wake-ups, we mark all interests as ready even - // though some may not have been fulfilled. - interests.keys().map(move |receiver| (*receiver, source.clone())) + let source = ecx + .machine + .blocking_io + .sources + .get(&fd_id) + .expect("Source should be registered"); + let fd = source.fd.upgrade().expect( + "Source file description shouldn't be destroyed whilst being registered", + ); + + assert_eq!(fd.id(), fd_id); + // Update the readiness of the source. + *fd.get_readiness_mut() |= BlockingIoSourceReadiness::from(event); + // Put FD into `event_fds` list. + fd }) .collect::>(); - // Deregister all ready sources as we only want to receive one event per receiver. - ready.iter().for_each(|(receiver, source)| self.deregister(source.id(), *receiver)); + // Update the epoll readiness for all source file descriptions which received an event. Also, + // unblock the threads which are blocked on such a source and whose interests are now fulfilled. + for fd in event_fds.into_iter() { + // Update epoll readiness for the `fd` source. + ecx.update_epoll_active_events(fd.clone(), false)?; - Ok(ready) + let source = + ecx.machine.blocking_io.sources.get(&fd.id()).expect( + "Source file description shouldn't be destroyed whilst being registered", + ); + + // List of all thread id's whose interests are currently fulfilled + // and which are blocked on the `fd` source. This also includes + // threads whose interests were already fulfilled before the + // `poll` invocation. + let threads = source + .blocked_threads + .iter() + .filter_map(|(thread_id, interest)| { + fd.get_readiness_mut().fulfills_interest(interest).then_some(*thread_id) + }) + .collect::>(); + + // Unblock all threads whose interests are currently fulfilled and + // which are blocked on the `fd` source. + threads + .into_iter() + .try_for_each(|thread_id| ecx.unblock_thread(thread_id, BlockReason::IO))?; + } + + interp_ok(Ok(())) } - /// Register an interest for a blocking I/O source. - /// - /// As the OS can always produce spurious wake-ups, it's the callers responsibility to - /// verify the requested I/O interests are really ready and to register again if they're not. - /// - /// It's assumed that no interest is already registered for this source with the same reason! - pub fn register( - &mut self, - source_fd: FileDescriptionRef, - receiver: InterestReceiver, - interest: Interest, - ) { + /// Return whether a source file description is currently registered in the + /// blocking I/O poll. + /// This can also be used to check whether a file description is a host + /// I/O source. + pub fn contains_source(&self, source_id: &FdId) -> bool { + self.sources.contains_key(source_id) + } + + /// Register a source file description to the blocking I/O poll. + pub fn register(&mut self, source_fd: FileDescriptionRef) { let poll = self.poll.as_ref().expect("Blocking I/O should not be called with isolation enabled"); let id = source_fd.id(); let token = Token(id.to_usize()); - let Some((_, current_interests)) = self.sources.get_mut(&id) else { - // The source is not yet registered. + // All possible interests. + // We only care about the readable and writable interests because those are the only + // interests which are available on all platforms. Internally, mio also + // registers an error interest. + let interest = Interest::READABLE | Interest::WRITABLE; - // Treat errors from registering as fatal. On UNIX hosts this can only - // fail due to system resource errors (e.g. ENOMEM or ENOSPC). - source_fd - .with_source(&mut |source| poll.registry().register(source, token, interest)) - .unwrap(); + // Treat errors from registering as fatal. On UNIX hosts this can only + // fail due to system resource errors (e.g. ENOMEM or ENOSPC) or when the source is already registered. + source_fd + .with_source(&mut |source| poll.registry().register(source, token, interest)) + .unwrap(); - self.sources.insert(id, (source_fd, FxHashMap::from_iter([(receiver, interest)]))); - return; + let source = BlockingIoSource { + fd: FileDescriptionRef::downgrade(&source_fd), + blocked_threads: BTreeMap::default(), }; - // The source is already registered. We need to check whether we need to - // reregister because the provided interest contains new interests for the source. - - let old_interest = - interest_union(current_interests).expect("Source should contain at least one interest"); - - current_interests - .try_insert(receiver, interest) - .unwrap_or_else(|_| panic!("Receiver should be unique")); - - let new_interest = old_interest.add(interest); - - // Reregister the source since the overall interests might have changed. - - // Treat errors from reregistering as fatal. On UNIX hosts this can only - // fail due to system resource errors (e.g. ENOMEM or ENOSPC). - source_fd - .with_source(&mut |source| poll.registry().reregister(source, token, new_interest)) - .unwrap(); + self.sources + .try_insert(id, source) + .unwrap_or_else(|_| panic!("Source should not already be registered")); } - /// Deregister an interest from a blocking I/O source. + /// Deregister a source file description from the blocking I/O poll. /// - /// The receiver is assumed to be registered for the provided source! - pub fn deregister(&mut self, source_id: FdId, receiver: InterestReceiver) { + /// It's assumed that the file description with id `source_id` is already + /// removed from the file description table. + pub fn deregister(&mut self, source_id: FdId, source: impl SourceFileDescription) { let poll = self.poll.as_ref().expect("Blocking I/O should not be called with isolation enabled"); - let token = Token(source_id.to_usize()); - let (fd, current_interests) = - self.sources.get_mut(&source_id).expect("Source should be registered"); + let stored_source = self.sources.remove(&source_id).expect("Source should be registered"); + // Ensure that the source file description is already removed from the file + // description table. + assert!( + stored_source.fd.upgrade().is_none(), + "Sources must only be deregistered when they are destroyed" + ); - current_interests - .remove(&receiver) - .unwrap_or_else(|| panic!("Receiver should be registered for source")); + // Because we only store `WeakFileDescriptionRef`s and the `stored_source` file description + // is already destroyed, the weak reference can no longer be upgraded. Thus, we cannot use + // it to deregister the source from the poll and instead use the `source` argument to deregister. - let Some(new_interest) = interest_union(current_interests) else { - // There are no longer any interests in this source. - // We can thus deregister the source from the poll. - - // Treat errors from deregistering as fatal. On UNIX hosts this can only - // fail due to system resource errors (e.g. ENOMEM or ENOSPC). - fd.with_source(&mut |source| poll.registry().deregister(source)).unwrap(); - self.sources.remove(&source_id); - return; - }; - - // Reregister the source since the overall interests might have changed. - - // Treat errors from reregistering as fatal. On UNIX hosts this can only + // Treat errors from deregistering as fatal. On UNIX hosts this can only // fail due to system resource errors (e.g. ENOMEM or ENOSPC). - fd.with_source(&mut |source| poll.registry().reregister(source, token, new_interest)) - .unwrap(); + source.with_source(&mut |source| poll.registry().deregister(source)).unwrap(); } -} -/// Get the union of all interests for a source. Returns `None` if the map is empty. -fn interest_union(interests: &FxHashMap) -> Option { - interests - .values() - .copied() - .fold(None, |acc, interest| acc.map(|acc: Interest| acc.add(interest)).or(Some(interest))) + /// Add a new blocked thread to a registered source. The thread gets unblocked + /// once its [`BlockingIoInterest`] is fulfilled when calling + /// [`BlockingIoManager::poll`]. + /// + /// It's assumed that the thread of `thread_id` isn't already blocked on + /// the source with id `source_id` and that this source is currently + /// registered. + fn add_blocked_thread( + &mut self, + source_id: FdId, + thread_id: ThreadId, + interest: BlockingIoInterest, + ) { + let source = self.sources.get_mut(&source_id).expect("Source should be registered"); + + source + .blocked_threads + .try_insert(thread_id, interest) + .expect("Thread cannot be blocked multiple times on the same source"); + } + + /// Remove a blocked thread from a registered source. + /// + /// It's assumed that the thread of `thread_id` is blocked on the + /// source with id `source_id` and that this source is currently + /// registered. + pub fn remove_blocked_thread(&mut self, source_id: FdId, thread_id: ThreadId) { + let source = self.sources.get_mut(&source_id).expect("Source should be registered"); + source.blocked_threads.remove(&thread_id).expect("Thread should be blocked on source"); + } } impl<'tcx> EvalContextExt<'tcx> for MiriInterpCx<'tcx> {} @@ -205,23 +339,39 @@ pub trait EvalContextExt<'tcx>: MiriInterpCxExt<'tcx> { /// are fulfilled or the optional timeout exceeded. /// The callback will be invoked when the thread gets unblocked. /// - /// There can be spurious wake-ups by the OS and thus it's the callers + /// Note that an error interest is implicitly added to `interest`. + /// This means that the thread will also be unblocked when the error + /// readiness gets set for the source even when the requested interest + /// might not be fulfilled. + /// + /// There can also be spurious wake-ups by the OS and thus it's the callers /// responsibility to verify that the requested I/O interests are /// really ready and to block again if they're not. + /// + /// It's the callers responsibility to remove the [`BlockingIoInterest`] + /// from the blocking I/O manager in the provided callback function. #[inline] fn block_thread_for_io( &mut self, - source_fd: FileDescriptionRef, - interests: Interest, + source_fd: FileDescriptionRef, + interest: BlockingIoInterest, timeout: Option<(TimeoutClock, TimeoutAnchor, Duration)>, callback: DynUnblockCallback<'tcx>, - ) { + ) -> InterpResult<'tcx> { let this = self.eval_context_mut(); - this.machine.blocking_io.register( - source_fd, - InterestReceiver::UnblockThread(this.active_thread()), - interests, - ); - this.block_thread(BlockReason::IO, timeout, callback); + + // We always have to do this since the thread will de-register itself. + this.machine.blocking_io.add_blocked_thread(source_fd.id(), this.active_thread(), interest); + + if source_fd.get_readiness_mut().fulfills_interest(&interest) { + // The requested readiness is currently already fulfilled for the provided source. + // Instead of actually blocking the thread, we just run the callback function. + callback.call(this, UnblockKind::Ready) + } 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); + interp_ok(()) + } } } diff --git a/src/tools/miri/src/concurrency/data_race.rs b/src/tools/miri/src/concurrency/data_race.rs index 76f735d457cf..06a1510feabf 100644 --- a/src/tools/miri/src/concurrency/data_race.rs +++ b/src/tools/miri/src/concurrency/data_race.rs @@ -1029,7 +1029,8 @@ pub fn new_allocation( | MiriMemoryKind::C | MiriMemoryKind::WinHeap | MiriMemoryKind::WinLocal - | MiriMemoryKind::Mmap, + | MiriMemoryKind::Mmap + | MiriMemoryKind::SocketAddress, ) | MemoryKind::Stack => { let (alloc_index, clocks) = global.active_thread_state(thread_mgr); diff --git a/src/tools/miri/src/concurrency/thread.rs b/src/tools/miri/src/concurrency/thread.rs index 3b0b2b26c192..3c2c2c025214 100644 --- a/src/tools/miri/src/concurrency/thread.rs +++ b/src/tools/miri/src/concurrency/thread.rs @@ -18,8 +18,7 @@ use rustc_target::spec::Os; use crate::concurrency::GlobalDataRaceHandler; -use crate::concurrency::blocking_io::InterestReceiver; -use crate::shims::tls; +use crate::shims::{Epoll, EpollEvalContextExt, FileDescriptionRef, tls}; use crate::*; #[derive(Clone, Copy, Debug, PartialEq)] @@ -108,7 +107,7 @@ pub enum BlockReason { /// Blocked on an InitOnce. InitOnce, /// Blocked on epoll. - Epoll, + Epoll { epfd: FileDescriptionRef }, /// Blocked on eventfd. Eventfd, /// Blocked on virtual socket. @@ -779,7 +778,9 @@ fn schedule(&mut self) -> InterpResult<'tcx, SchedulingAction> { .filter(|(_id, thread)| thread.state.is_enabled()); // Pick a new thread, and switch to it. let new_thread = if thread_manager.fixed_scheduling { - threads_iter.next() + let next = threads_iter.next(); + drop(threads_iter); + next } else { threads_iter.choose(rng) }; @@ -800,48 +801,56 @@ fn schedule(&mut self) -> InterpResult<'tcx, SchedulingAction> { if thread_manager.threads[thread_manager.active_thread].state.is_enabled() { return interp_ok(SchedulingAction::ExecuteStep); } + // We have not found a thread to execute. - if thread_manager.threads.iter().all(|thread| thread.state.is_terminated()) { + let threads = &this.machine.threads.threads; + + if threads.iter().all(|thread| thread.state.is_terminated()) { unreachable!("all threads terminated without the main thread terminating?!"); } else if let Some(sleep_time) = potential_sleep_time { // All threads are currently blocked, but we have unexecuted // timeout_callbacks, which may unblock some of the threads. Hence, // sleep until the first callback. interp_ok(SchedulingAction::SleepAndWaitForIo(Some(sleep_time))) - } else if thread_manager - .threads - .iter() - .any(|thread| thread.state.is_blocked_on(&BlockReason::IO)) - { - // At least one thread is blocked on host I/O but doesn't - // have a timeout set. Hence, we sleep indefinitely in the - // hope that eventually an I/O event for this thread happens. + } else if threads.iter().any(|thread| this.is_thread_blocked_on_host(thread)) { + // At least one thread doesn't have a timeout set, and is blocked on host I/O or is waiting on an + // epoll instance which contains a host source interest. Hence, we sleep indefinitely in the + // hope that eventually an I/O event happens. interp_ok(SchedulingAction::SleepAndWaitForIo(None)) } else { throw_machine_stop!(TerminationInfo::GlobalDeadlock); } } + /// Check whether the provided thread is currently blocked on host I/O. + /// This means, it's either blocked on an I/O operation directly or it's + /// blocked on an epoll instance which contains a host source interest. + fn is_thread_blocked_on_host(&self, thread: &Thread<'tcx>) -> bool { + let this = self.eval_context_ref(); + match &thread.state { + ThreadState::Blocked { reason: BlockReason::IO, .. } => true, + ThreadState::Blocked { reason: BlockReason::Epoll { epfd }, .. } => + this.has_epoll_host_interests(epfd), + _ => false, + } + } + /// Poll for I/O events until either an I/O event happened or the timeout expired. /// The different timeout values are described in [`BlockingIoManager::poll`]. + /// + /// Unblocks all threads which are blocked on I/O and whose I/O interests + /// are currently fulfilled. fn poll_and_unblock(&mut self, timeout: Option) -> InterpResult<'tcx> { let this = self.eval_context_mut(); - let ready = match this.machine.blocking_io.poll(timeout) { - Ok(ready) => ready, + match BlockingIoManager::poll(this, timeout)? { + Ok(_) => interp_ok(()), // We can ignore errors originating from interrupts; that's just a spurious wakeup. - Err(e) if e.kind() == io::ErrorKind::Interrupted => return interp_ok(()), + Err(e) if e.kind() == io::ErrorKind::Interrupted => interp_ok(()), // For other errors we panic. On Linux and BSD hosts this should only be // reachable when a system resource error (e.g. ENOMEM or ENOSPC) occurred. Err(e) => panic!("unexpected error while polling: {e}"), - }; - - ready.into_iter().try_for_each(|(receiver, _source)| { - match receiver { - InterestReceiver::UnblockThread(thread_id) => - this.unblock_thread(thread_id, BlockReason::IO), - } - }) + } } /// Find all threads with expired timeouts, unblock them and execute their timeout callbacks. diff --git a/src/tools/miri/src/diagnostics.rs b/src/tools/miri/src/diagnostics.rs index 55cc1295d1df..25bbe36b3fe6 100644 --- a/src/tools/miri/src/diagnostics.rs +++ b/src/tools/miri/src/diagnostics.rs @@ -154,6 +154,10 @@ pub enum NonHaltingDiagnostic { effective_failure_ordering: AtomicReadOrd, }, FileInProcOpened, + ConnectingSocketGetsockname, + SocketAddressResolution { + error: std::io::Error, + }, } /// Level of Miri specific diagnostics @@ -660,6 +664,10 @@ pub fn emit_diagnostic(&self, e: NonHaltingDiagnostic) { | WeakMemoryOutdatedLoad { .. } => ("tracking was triggered here".to_string(), DiagLevel::Note), FileInProcOpened => ("open a file in `/proc`".to_string(), DiagLevel::Warning), + ConnectingSocketGetsockname => + ("Called `getsockname` on connecting socket".to_string(), DiagLevel::Warning), + SocketAddressResolution { .. } => + ("error during address resolution".to_string(), DiagLevel::Warning), }; let title = match &e { @@ -708,12 +716,27 @@ pub fn emit_diagnostic(&self, e: NonHaltingDiagnostic) { format!("GenMC currently does not model the failure ordering for `compare_exchange`. {was_upgraded_msg}. Miri with GenMC might miss bugs related to this memory access.") } FileInProcOpened => format!("files in `/proc` can bypass the Abstract Machine and might not work properly in Miri"), + ConnectingSocketGetsockname => format!("connecting sockets return unspecified socket addresses on Windows hosts"), + SocketAddressResolution { error } => format!("address resolution failed: {error}"), }; let notes = match &e { ProgressReport { block_count } => { vec![note!("so far, {block_count} basic blocks have been executed")] } + ConnectingSocketGetsockname => + vec![ + note!( + "Windows hosts do not provide `local_addr` information while the socket is still connecting, which might break the assumptions of code compiled for Unix targets" + ), + note!( + "an unspecified socket address (e.g. `0.0.0.0:0`) will be returned instead" + ), + ], + SocketAddressResolution { .. } => + vec![note!( + "Miri cannot return proper error information from this call; only a generic error code is being returned" + )], _ => vec![], }; diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs index 512ea6ab1a2f..de94f4a27a20 100644 --- a/src/tools/miri/src/helpers.rs +++ b/src/tools/miri/src/helpers.rs @@ -905,8 +905,7 @@ fn frame_in_std(&self) -> bool { let frame_crate = this.tcx.def_path(instance.def_id()).krate; let crate_name = this.tcx.crate_name(frame_crate); let crate_name = crate_name.as_str(); - // On miri-test-libstd, the name of the crate is different. - crate_name == "std" || crate_name == "std_miri_test" + crate_name == "std" } /// Mark a machine allocation that was just created as immutable. diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs index 86816e5f789b..944ff933ac9b 100644 --- a/src/tools/miri/src/lib.rs +++ b/src/tools/miri/src/lib.rs @@ -134,7 +134,10 @@ pub mod native_lib { BorTag, BorrowTrackerMethod, EvalContextExt as _, TreeBorrowsParams, }; pub use crate::clock::{Instant, MonotonicClock}; -pub use crate::concurrency::blocking_io::{BlockingIoManager, EvalContextExt as _, WithSource}; +pub use crate::concurrency::blocking_io::{ + BlockingIoInterest, BlockingIoManager, BlockingIoSourceReadiness, EvalContextExt as _, + SourceFileDescription, +}; pub use crate::concurrency::cpu_affinity::MAX_CPUS; pub use crate::concurrency::data_race::{ AtomicFenceOrd, AtomicReadOrd, AtomicRwOrd, AtomicWriteOrd, EvalContextExt as _, diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index 6c4c3fa14184..21f505719270 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -202,8 +202,10 @@ pub enum MiriMemoryKind { /// Memory for thread-local statics. /// This memory may leak. Tls, - /// Memory mapped directly by the program + /// Memory mapped directly by the program. Mmap, + /// Memory allocated for `getaddrinfo` result. + SocketAddress, } impl From for MemoryKind { @@ -219,7 +221,7 @@ fn may_leak(self) -> bool { use self::MiriMemoryKind::*; match self { Rust | Miri | C | WinHeap | WinLocal | Runtime => false, - Machine | Global | ExternStatic | Tls | Mmap => true, + Machine | Global | ExternStatic | Tls | Mmap | SocketAddress => true, } } } @@ -232,7 +234,7 @@ fn should_save_allocation_span(self) -> bool { // Heap allocations are fine since the `Allocation` is created immediately. Rust | Miri | C | WinHeap | WinLocal | Mmap => true, // Everything else is unclear, let's not show potentially confusing spans. - Machine | Global | ExternStatic | Tls | Runtime => false, + Machine | Global | ExternStatic | Tls | Runtime | SocketAddress => false, } } } @@ -252,6 +254,7 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { ExternStatic => write!(f, "extern static"), Tls => write!(f, "thread-local static"), Mmap => write!(f, "mmap"), + SocketAddress => write!(f, "socket address"), } } } diff --git a/src/tools/miri/src/shims/files.rs b/src/tools/miri/src/shims/files.rs index 04b84e6f3e67..26f98a5f2b19 100644 --- a/src/tools/miri/src/shims/files.rs +++ b/src/tools/miri/src/shims/files.rs @@ -61,6 +61,14 @@ pub fn id(&self) -> FdId { } } +impl PartialEq for FileDescriptionRef { + fn eq(&self, other: &Self) -> bool { + self.0.id == other.0.id + } +} + +impl Eq for FileDescriptionRef {} + /// Holds a weak reference to the actual file description. #[derive(Debug)] pub struct WeakFileDescriptionRef(Weak>); diff --git a/src/tools/miri/src/shims/io_error.rs b/src/tools/miri/src/shims/io_error.rs index cc9459318d7f..2e5410429efe 100644 --- a/src/tools/miri/src/shims/io_error.rs +++ b/src/tools/miri/src/shims/io_error.rs @@ -103,6 +103,89 @@ fn from(value: Scalar) -> Self { ("EAGAIN", WouldBlock), ] }; +// On Unix hosts are can avoid round-tripping via `ErrorKind`, which can preserve more +// details and leads to nicer output in `strerror_r`. +#[cfg(unix)] +const UNIX_ERRNO_TABLE: &[(&str, libc::c_int)] = &[ + ("E2BIG", libc::E2BIG), + ("EACCES", libc::EACCES), + ("EADDRINUSE", libc::EADDRINUSE), + ("EADDRNOTAVAIL", libc::EADDRNOTAVAIL), + ("EAFNOSUPPORT", libc::EAFNOSUPPORT), + ("EAGAIN", libc::EAGAIN), + ("EALREADY", libc::EALREADY), + ("EBADF", libc::EBADF), + ("EBADMSG", libc::EBADMSG), + ("EBUSY", libc::EBUSY), + ("ECANCELED", libc::ECANCELED), + ("ECHILD", libc::ECHILD), + ("ECONNABORTED", libc::ECONNABORTED), + ("ECONNREFUSED", libc::ECONNREFUSED), + ("ECONNRESET", libc::ECONNRESET), + ("EDEADLK", libc::EDEADLK), + ("EDESTADDRREQ", libc::EDESTADDRREQ), + ("EDOM", libc::EDOM), + ("EDQUOT", libc::EDQUOT), + ("EEXIST", libc::EEXIST), + ("EFAULT", libc::EFAULT), + ("EFBIG", libc::EFBIG), + ("EHOSTUNREACH", libc::EHOSTUNREACH), + ("EIDRM", libc::EIDRM), + ("EILSEQ", libc::EILSEQ), + ("EINPROGRESS", libc::EINPROGRESS), + ("EINTR", libc::EINTR), + ("EINVAL", libc::EINVAL), + ("EIO", libc::EIO), + ("EISCONN", libc::EISCONN), + ("EISDIR", libc::EISDIR), + ("ELOOP", libc::ELOOP), + ("EMFILE", libc::EMFILE), + ("EMLINK", libc::EMLINK), + ("EMSGSIZE", libc::EMSGSIZE), + ("EMULTIHOP", libc::EMULTIHOP), + ("ENAMETOOLONG", libc::ENAMETOOLONG), + ("ENETDOWN", libc::ENETDOWN), + ("ENETRESET", libc::ENETRESET), + ("ENETUNREACH", libc::ENETUNREACH), + ("ENFILE", libc::ENFILE), + ("ENOBUFS", libc::ENOBUFS), + ("ENODEV", libc::ENODEV), + ("ENOENT", libc::ENOENT), + ("ENOEXEC", libc::ENOEXEC), + ("ENOLCK", libc::ENOLCK), + ("ENOLINK", libc::ENOLINK), + ("ENOMEM", libc::ENOMEM), + ("ENOMSG", libc::ENOMSG), + ("ENOPROTOOPT", libc::ENOPROTOOPT), + ("ENOSPC", libc::ENOSPC), + ("ENOSYS", libc::ENOSYS), + ("ENOTCONN", libc::ENOTCONN), + ("ENOTDIR", libc::ENOTDIR), + ("ENOTEMPTY", libc::ENOTEMPTY), + ("ENOTRECOVERABLE", libc::ENOTRECOVERABLE), + ("ENOTSOCK", libc::ENOTSOCK), + ("ENOTSUP", libc::ENOTSUP), + ("ENOTTY", libc::ENOTTY), + ("ENXIO", libc::ENXIO), + ("EOPNOTSUPP", libc::EOPNOTSUPP), + ("EOVERFLOW", libc::EOVERFLOW), + ("EOWNERDEAD", libc::EOWNERDEAD), + ("EPERM", libc::EPERM), + ("EPIPE", libc::EPIPE), + ("EPROTO", libc::EPROTO), + ("EPROTONOSUPPORT", libc::EPROTONOSUPPORT), + ("EPROTOTYPE", libc::EPROTOTYPE), + ("ERANGE", libc::ERANGE), + ("EROFS", libc::EROFS), + ("ESOCKTNOSUPPORT", libc::ESOCKTNOSUPPORT), + ("ESPIPE", libc::ESPIPE), + ("ESRCH", libc::ESRCH), + ("ESTALE", libc::ESTALE), + ("ETIMEDOUT", libc::ETIMEDOUT), + ("ETXTBSY", libc::ETXTBSY), + ("EWOULDBLOCK", libc::EWOULDBLOCK), + ("EXDEV", libc::EXDEV), +]; // This mapping should match `decode_error_kind` in // . const WINDOWS_IO_ERROR_TABLE: &[(&str, std::io::ErrorKind)] = { @@ -246,8 +329,8 @@ fn get_last_error(&mut self) -> InterpResult<'tcx, Scalar> { this.read_scalar(&errno_place) } - /// This function tries to produce the most similar OS error from the `std::io::ErrorKind` - /// as a platform-specific errnum. + /// This function converts host errors to target errors. It tries to produce the most similar OS + /// error from the `std::io::ErrorKind` as a platform-specific errnum. fn io_error_to_errnum(&self, err: std::io::Error) -> InterpResult<'tcx, Scalar> { let this = self.eval_context_ref(); let target = &this.tcx.sess.target; @@ -274,27 +357,38 @@ fn io_error_to_errnum(&self, err: std::io::Error) -> InterpResult<'tcx, Scalar> } } - /// The inverse of `io_error_to_errnum`. + /// The inverse of `io_error_to_errnum`: it converts target errors to host errors. + /// This is done in a best-effort way. #[expect(clippy::needless_return)] fn try_errnum_to_io_error( &self, - errnum: Scalar, - ) -> InterpResult<'tcx, Option> { + target_errnum: Scalar, + ) -> InterpResult<'tcx, Option> { let this = self.eval_context_ref(); let target = &this.tcx.sess.target; if target.families.iter().any(|f| f == "unix") { - let errnum = errnum.to_i32()?; + let target_errnum = target_errnum.to_i32()?; + // If the host is also unix, we try to translate the errno directly. + // That lets us use `Error::from_raw_os_error`, which has a much better `Display` + // impl than what we get by going through `ErrorKind`. + #[cfg(unix)] + for &(name, errno) in UNIX_ERRNO_TABLE { + if target_errnum == this.eval_libc_i32(name) { + return interp_ok(Some(io::Error::from_raw_os_error(errno))); + } + } + // For other hosts or other constants, we fall back to translating via `ErrorKind`. for &(name, kind) in UNIX_IO_ERROR_TABLE { - if errnum == this.eval_libc_i32(name) { - return interp_ok(Some(kind)); + if target_errnum == this.eval_libc_i32(name) { + return interp_ok(Some(kind.into())); } } return interp_ok(None); } else if target.families.iter().any(|f| f == "windows") { - let errnum = errnum.to_u32()?; + let target_errnum = target_errnum.to_u32()?; for &(name, kind) in WINDOWS_IO_ERROR_TABLE { - if errnum == this.eval_windows("c", name).to_u32()? { - return interp_ok(Some(kind)); + if target_errnum == this.eval_windows("c", name).to_u32()? { + return interp_ok(Some(kind.into())); } } return interp_ok(None); diff --git a/src/tools/miri/src/shims/mod.rs b/src/tools/miri/src/shims/mod.rs index e15134fa01bb..551f7b2eff34 100644 --- a/src/tools/miri/src/shims/mod.rs +++ b/src/tools/miri/src/shims/mod.rs @@ -23,10 +23,10 @@ pub mod tls; pub mod unwind; -pub use self::files::{FdId, FdTable, FileDescriptionRef}; +pub use self::files::{FdId, FdTable, FileDescription, FileDescriptionRef, WeakFileDescriptionRef}; #[cfg(all(feature = "native-lib", unix))] pub use self::native_lib::trace::{init_sv, register_retcode_sv}; -pub use self::unix::{DirTable, EpollInterestTable}; +pub use self::unix::{DirTable, Epoll, EpollEvalContextExt, EpollInterestTable}; /// What needs to be done after emulating an item (a shim or an intrinsic) is done. pub enum EmulateItemResult { diff --git a/src/tools/miri/src/shims/time.rs b/src/tools/miri/src/shims/time.rs index 2c2b029a1232..66bf50e5f18d 100644 --- a/src/tools/miri/src/shims/time.rs +++ b/src/tools/miri/src/shims/time.rs @@ -247,6 +247,7 @@ fn QueryPerformanceCounter( let qpc = i64::try_from(duration.as_nanos()).map_err(|_| { err_unsup_format!("programs running longer than 2^63 nanoseconds are not supported") })?; + this.write_scalar( Scalar::from_i64(qpc), &this.deref_pointer_as(lpPerformanceCount_op, this.machine.layouts.i64)?, diff --git a/src/tools/miri/src/shims/unix/fd.rs b/src/tools/miri/src/shims/unix/fd.rs index 5e5f7d6bc3b8..15b13d3ba57a 100644 --- a/src/tools/miri/src/shims/unix/fd.rs +++ b/src/tools/miri/src/shims/unix/fd.rs @@ -10,7 +10,7 @@ use crate::shims::files::FileDescription; use crate::shims::sig::check_min_vararg_count; -use crate::shims::unix::linux_like::epoll::EpollEvents; +use crate::shims::unix::linux_like::epoll::EpollReadiness; use crate::shims::unix::*; use crate::*; @@ -77,7 +77,7 @@ fn ioctl<'tcx>( } /// Return which epoll events are currently active. - fn epoll_active_events<'tcx>(&self) -> InterpResult<'tcx, EpollEvents> { + fn epoll_active_events<'tcx>(&self) -> InterpResult<'tcx, EpollReadiness> { throw_unsup_format!("{}: epoll does not support this file description", self.name()); } } diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs index dc2ce5da2828..748987360be9 100644 --- a/src/tools/miri/src/shims/unix/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/foreign_items.rs @@ -362,6 +362,26 @@ fn emulate_foreign_item_inner( let result = this.stat(path, buf)?; this.write_scalar(result, dest)?; } + "chmod" => { + let [path, mode] = this.check_shim_sig( + shim_sig!(extern "C" fn(*const _, libc::mode_t) -> i32), + link_name, + abi, + args, + )?; + let result = this.chmod(path, mode)?; + this.write_scalar(result, dest)?; + } + "fchmod" => { + let [fd, mode] = this.check_shim_sig( + shim_sig!(extern "C" fn(i32, libc::mode_t) -> i32), + link_name, + abi, + args, + )?; + let result = this.fchmod(fd, mode)?; + this.write_scalar(result, dest)?; + } "rename" => { // FIXME: This does not have a direct test (#3179). let [oldpath, newpath] = this.check_shim_sig( @@ -536,7 +556,7 @@ fn emulate_foreign_item_inner( this.write_scalar(result, dest)?; } - // Unnamed sockets and pipes + // Sockets and pipes "socketpair" => { let [domain, type_, protocol, sv] = this.check_shim_sig( shim_sig!(extern "C" fn(i32, i32, i32, *mut _) -> i32), @@ -661,6 +681,17 @@ fn emulate_foreign_item_inner( this.setsockopt(socket, level, option_name, option_value, option_len)?; this.write_scalar(result, dest)?; } + "getsockopt" => { + let [socket, level, option_name, option_value, option_len] = this.check_shim_sig( + shim_sig!(extern "C" fn(i32, i32, i32, *mut _, *mut _) -> i32), + link_name, + abi, + args, + )?; + let result = + this.getsockopt(socket, level, option_name, option_value, option_len)?; + this.write_scalar(result, dest)?; + } "getsockname" => { let [socket, address, address_len] = this.check_shim_sig( shim_sig!(extern "C" fn(i32, *mut _, *mut _) -> i32), @@ -690,6 +721,25 @@ fn emulate_foreign_item_inner( let result = this.shutdown(sockfd, how)?; this.write_scalar(result, dest)?; } + "getaddrinfo" => { + let [node, service, hints, res] = this.check_shim_sig( + shim_sig!(extern "C" fn(*const _, *const _, *const _, *mut _) -> i32), + link_name, + abi, + args, + )?; + let result = this.getaddrinfo(node, service, hints, res)?; + this.write_scalar(result, dest)?; + } + "freeaddrinfo" => { + let [res] = this.check_shim_sig( + shim_sig!(extern "C" fn(*mut _) -> ()), + link_name, + abi, + args, + )?; + this.freeaddrinfo(res)?; + } // Time "gettimeofday" => { diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index b1d39513065c..8df54616b562 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -2,10 +2,7 @@ use std::borrow::Cow; use std::ffi::OsString; -use std::fs::{ - self, DirBuilder, File, FileType, OpenOptions, TryLockError, read_dir, remove_dir, remove_file, - rename, -}; +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::time::SystemTime; @@ -236,15 +233,10 @@ fn write_stat_buf( // which can be different between the libc used by std and the libc used by everyone else. let buf = this.deref_pointer(buf_op)?; - // `libc::S_IF*` constants are of type `mode_t`, which varies in width across targets - // (`u16` on macOS, `u32` on Linux). Read the scalar using `mode_t`'s size on the target. - let mode_t_size = this.libc_ty_layout("mode_t").size; - let mode: u32 = metadata.mode.to_uint(mode_t_size)?.try_into().unwrap(); - this.write_int_fields_named( &[ ("st_dev", metadata.dev.unwrap_or(0).into()), - ("st_mode", mode.into()), + ("st_mode", metadata.mode.into()), ("st_nlink", metadata.nlink.unwrap_or(0).into()), ("st_ino", metadata.ino.unwrap_or(0).into()), ("st_uid", metadata.uid.unwrap_or(0).into()), @@ -346,6 +338,17 @@ fn dir_entry_fields( }, }) } + + #[cfg(unix)] + fn host_permissions_from_mode(&self, mode: u32) -> InterpResult<'tcx, fs::Permissions> { + use std::os::unix::fs::PermissionsExt; + interp_ok(fs::Permissions::from_mode(mode)) + } + + #[cfg(not(unix))] + fn host_permissions_from_mode(&self, _mode: u32) -> InterpResult<'tcx, fs::Permissions> { + throw_unsup_format!("setting file permissions is only supported on Unix hosts") + } } impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} @@ -547,7 +550,7 @@ fn unlink(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied); } - let result = remove_file(path).map(|_| 0); + let result = fs::remove_file(path).map(|_| 0); interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?)) } @@ -766,15 +769,6 @@ fn linux_statx( mask |= this.eval_libc_u32("STATX_BLOCKS"); } - // `statx.stx_mode` is `__u16`. `libc::S_IF*` are of type `mode_t`, which varies in - // width across targets (`u16` on macOS, `u32` on Linux). Read using `mode_t`'s size. - let mode_t_size = this.libc_ty_layout("mode_t").size; - let mode: u16 = metadata - .mode - .to_uint(mode_t_size)? - .try_into() - .unwrap_or_else(|_| bug!("libc contains bad value for constant")); - // We need to set the corresponding bits of `mask` if the access, creation and modification // times were available. Otherwise we let them be zero. let (access_sec, access_nsec) = metadata @@ -805,12 +799,12 @@ fn linux_statx( this.write_int_fields_named( &[ ("stx_mask", mask.into()), + ("stx_mode", metadata.mode.into()), ("stx_blksize", metadata.blksize.unwrap_or(0).into()), ("stx_attributes", 0), ("stx_nlink", metadata.nlink.unwrap_or(0).into()), ("stx_uid", metadata.uid.unwrap_or(0).into()), ("stx_gid", metadata.gid.unwrap_or(0).into()), - ("stx_mode", mode.into()), ("stx_ino", metadata.ino.unwrap_or(0).into()), ("stx_size", metadata.size.into()), ("stx_blocks", metadata.blocks.unwrap_or(0).into()), @@ -858,6 +852,47 @@ fn linux_statx( interp_ok(Scalar::from_i32(0)) } + fn chmod(&mut self, path_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + + let path_ptr = this.read_pointer(path_op)?; + let mode = this.read_scalar(mode_op)?.to_uint(this.libc_ty_layout("mode_t").size)?; + + if this.ptr_is_null(path_ptr)? { + return this.set_last_error_and_return_i32(LibcError("EFAULT")); + } + let path = this.read_path_from_c_str(path_ptr)?; + + let permissions = this.host_permissions_from_mode(mode.try_into().unwrap())?; + if let Err(err) = fs::set_permissions(path, permissions) { + return this.set_last_error_and_return_i32(IoError::HostError(err)); + } + + interp_ok(Scalar::from_i32(0)) + } + + fn fchmod(&mut self, fd_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + + let fd_num = this.read_scalar(fd_op)?.to_i32()?; + let mode = this.read_scalar(mode_op)?.to_uint(this.libc_ty_layout("mode_t").size)?; + + let Some(fd) = this.machine.fds.get(fd_num) else { + return this.set_last_error_and_return_i32(LibcError("EBADF")); + }; + let Some(file) = fd.downcast::() else { + // The docs don't talk about what happens for non-regular files... + throw_unsup_format!("`fchmod` is only supported on regular files") + }; + + let permissions = this.host_permissions_from_mode(mode.try_into().unwrap())?; + if let Err(err) = file.file.set_permissions(permissions) { + return this.set_last_error_and_return_i32(IoError::HostError(err)); + } + + interp_ok(Scalar::from_i32(0)) + } + fn rename( &mut self, oldpath_op: &OpTy<'tcx>, @@ -881,7 +916,7 @@ fn rename( return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied); } - let result = rename(oldpath, newpath).map(|_| 0); + let result = fs::rename(oldpath, newpath).map(|_| 0); interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?)) } @@ -931,7 +966,7 @@ fn rmdir(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied); } - let result = remove_dir(path).map(|_| 0i32); + let result = fs::remove_dir(path).map(|_| 0i32); interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?)) } @@ -948,7 +983,7 @@ fn opendir(&mut self, name_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { return interp_ok(Scalar::null_ptr(this)); } - let result = read_dir(name); + let result = fs::read_dir(name); match result { Ok(dir_iter) => { @@ -1684,7 +1719,8 @@ fn file_type_to_mode_name(file_type: std::fs::FileType) -> &'static str { /// expose it. `statx` must only advertise the corresponding `STATX_*` bit when the field is `Some`; /// legacy `stat` writes zero for `None` to preserve the old fallback behavior. struct FileMetadata { - mode: Scalar, + /// This holds both the file type (dir, regular, symlink, ...) and permissions. + mode: u32, size: u64, created: Option<(u64, u32)>, accessed: Option<(u64, u32)>, @@ -1728,6 +1764,9 @@ fn synthetic<'tcx>( mode_name: &str, ) -> InterpResult<'tcx, Result> { let mode = ecx.eval_libc(mode_name); + let mode: u32 = mode.to_uint(ecx.libc_ty_layout("mode_t").size)?.try_into().unwrap(); + // We observed 0x777 on sockets and 0x600 on pipes... + let mode = mode | 0o666; interp_ok(Ok(FileMetadata { mode, size: 0, @@ -1757,6 +1796,7 @@ fn from_meta<'tcx>( let file_type = metadata.file_type(); let mode = ecx.eval_libc(file_type_to_mode_name(file_type)); + let mut mode = mode.to_uint(ecx.libc_ty_layout("mode_t").size)?.try_into().unwrap(); let size = metadata.len(); @@ -1769,6 +1809,8 @@ fn from_meta<'tcx>( cfg_select! { unix => { use std::os::unix::fs::MetadataExt; + use std::os::unix::fs::PermissionsExt; + let dev = metadata.dev(); let ino = metadata.ino(); let nlink = metadata.nlink(); @@ -1777,6 +1819,8 @@ fn from_meta<'tcx>( let blksize = metadata.blksize(); let blocks = metadata.blocks(); + mode |= metadata.permissions().mode(); + interp_ok(Ok(FileMetadata { mode, size, @@ -1792,20 +1836,25 @@ fn from_meta<'tcx>( blocks: Some(blocks), })) } - _ => interp_ok(Ok(FileMetadata { - mode, - size, - created, - accessed, - modified, - dev: None, - ino: None, - nlink: None, - uid: None, - gid: None, - blksize: None, - blocks: None, - })), + _ => { + // Emulate "everyone can read" or "everyone can read and write". + mode |= if metadata.permissions().readonly() { 0o111 } else { 0o333 }; + + interp_ok(Ok(FileMetadata { + mode, + size, + created, + accessed, + modified, + dev: None, + ino: None, + nlink: None, + uid: None, + gid: None, + blksize: None, + blocks: None, + })) + }, } } } diff --git a/src/tools/miri/src/shims/unix/linux/foreign_items.rs b/src/tools/miri/src/shims/unix/linux/foreign_items.rs index f679f27aed23..e42bf900acef 100644 --- a/src/tools/miri/src/shims/unix/linux/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/linux/foreign_items.rs @@ -4,7 +4,6 @@ use rustc_target::callconv::FnAbi; use self::shims::unix::linux::mem::EvalContextExt as _; -use self::shims::unix::linux_like::epoll::EvalContextExt as _; use self::shims::unix::linux_like::eventfd::EvalContextExt as _; use self::shims::unix::linux_like::syscall::syscall; use crate::machine::{SIGRTMAX, SIGRTMIN}; @@ -247,6 +246,17 @@ fn emulate_foreign_item_inner( this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; this.write_null(dest)?; } + "gnu_get_libc_version" + if this.frame_in_std() + && this.tcx.sess.target.env == rustc_target::spec::Env::Gnu => + { + let [] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; + // We have to be at least version 2.26 so that std does not call `res_init`. + // This returns a C string, so we have to add a null terminator. + let version = "2.26\0"; + let version = this.allocate_str_dedup(version)?; + this.write_pointer(version.ptr(), dest)?; + } _ => return interp_ok(EmulateItemResult::NotSupported), }; 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 bd07e13d47fb..1758c04c8981 100644 --- a/src/tools/miri/src/shims/unix/linux_like/epoll.rs +++ b/src/tools/miri/src/shims/unix/linux_like/epoll.rs @@ -17,7 +17,7 @@ /// An `Epoll` file descriptor connects file handles and epoll events #[derive(Debug, Default)] -struct Epoll { +pub struct Epoll { /// A map of EpollEventInterests registered under this epoll instance. Each entry is /// differentiated using FdId and file descriptor value. interest_list: RefCell>, @@ -55,9 +55,9 @@ pub struct EpollEventInterest { data: u64, } -/// EpollReadyEvents reflects the readiness of a file description. +/// Struct reflecting the readiness of a file description. #[derive(Debug)] -pub struct EpollEvents { +pub struct EpollReadiness { /// The associated file is available for read(2) operations, in the sense that a read will not block. /// (I.e., returning EOF is considered "ready".) pub epollin: bool, @@ -76,9 +76,9 @@ pub struct EpollEvents { pub epollerr: bool, } -impl EpollEvents { - pub fn new() -> Self { - EpollEvents { +impl EpollReadiness { + pub fn empty() -> Self { + EpollReadiness { epollin: false, epollout: false, epollrdhup: false, @@ -114,6 +114,19 @@ pub fn get_event_bitmask<'tcx>(&self, ecx: &MiriInterpCx<'tcx>) -> u32 { } } +// Best-effort mapping from cross platform readiness to epoll readiness. +impl From<&BlockingIoSourceReadiness> for EpollReadiness { + fn from(readiness: &BlockingIoSourceReadiness) -> Self { + Self { + epollin: readiness.readable, + epollout: readiness.writable, + epollrdhup: readiness.read_closed, + epollhup: readiness.write_closed, + epollerr: readiness.error, + } + } +} + impl FileDescription for Epoll { fn name(&self) -> &'static str { "epoll" @@ -354,11 +367,13 @@ fn epoll_ctl( interest.data = data; } + let active_events = fd_ref.as_unix(this).epoll_active_events()?.get_event_bitmask(this); + // Deliver events for the new interest. update_readiness( this, &epfd, - fd_ref.as_unix(this).epoll_active_events()?.get_event_bitmask(this), + active_events, /* force_edge */ true, move |callback| { // Need to release the RefCell when this closure returns, so we have to move @@ -479,7 +494,7 @@ fn epoll_wait( // This means there'll be a leak if we never wake up, but that anyway would imply // a thread is permanently blocked so this is fine. this.block_thread( - BlockReason::Epoll, + BlockReason::Epoll { epfd: epfd.clone() }, timeout, callback!( @capture<'tcx> { @@ -547,6 +562,17 @@ fn update_epoll_active_events( interp_ok(()) } + + /// Recursively check whether the [`Epoll`] file description contains + /// interests which are host I/O source file descriptions. + fn has_epoll_host_interests(&self, epfd: &FileDescriptionRef) -> bool { + let this = self.eval_context_ref(); + epfd.interest_list.borrow().iter().any(|((fd_id, _fd_num), _)| { + // By looking up whether the file description is currently registered, + // we get whether it's a host I/O source file description. + this.machine.blocking_io.contains_source(fd_id) + }) + } } /// Call this when the interests denoted by `for_each_interest` have their active event set changed @@ -557,7 +583,7 @@ fn update_epoll_active_events( /// be waking up threads which might require access to those `RefCell`. fn update_readiness<'tcx>( ecx: &mut MiriInterpCx<'tcx>, - epoll: &Epoll, + epoll: &FileDescriptionRef, active_events: u32, force_edge: bool, for_each_interest: impl FnOnce( @@ -587,7 +613,7 @@ fn update_readiness<'tcx>( && let Some(thread_id) = epoll.queue.borrow_mut().pop_front() { drop(ready_set); // release the "lock" so the unblocked thread can have it - ecx.unblock_thread(thread_id, BlockReason::Epoll)?; + ecx.unblock_thread(thread_id, BlockReason::Epoll { epfd: epoll.clone() })?; ready_set = epoll.ready_set.borrow_mut(); } @@ -612,7 +638,7 @@ fn return_ready_list<'tcx>( for (key, interest) in interest_list.iter() { // Ensure this matches the latest readiness of this FD. // We have to do an FD lookup by ID for this. The FdNum might be already closed. - let fd = &ecx.machine.fds.fds.values().find(|fd| fd.id() == key.0).unwrap(); + let fd = ecx.machine.fds.fds.values().find(|fd| fd.id() == key.0).unwrap(); let current_active = fd.as_unix(ecx).epoll_active_events()?.get_event_bitmask(ecx); assert_eq!(interest.active_events, current_active & interest.relevant_events); } diff --git a/src/tools/miri/src/shims/unix/linux_like/eventfd.rs b/src/tools/miri/src/shims/unix/linux_like/eventfd.rs index 7cccbd0e275c..76374ca24d0d 100644 --- a/src/tools/miri/src/shims/unix/linux_like/eventfd.rs +++ b/src/tools/miri/src/shims/unix/linux_like/eventfd.rs @@ -6,7 +6,7 @@ use crate::concurrency::VClock; use crate::shims::files::{FdId, FileDescription, FileDescriptionRef, WeakFileDescriptionRef}; use crate::shims::unix::UnixFileDescription; -use crate::shims::unix::linux_like::epoll::{EpollEvents, EvalContextExt as _}; +use crate::shims::unix::linux_like::epoll::{EpollReadiness, EvalContextExt as _}; use crate::*; /// Maximum value that the eventfd counter can hold. @@ -114,14 +114,14 @@ fn as_unix<'tcx>(&self, _ecx: &MiriInterpCx<'tcx>) -> &dyn UnixFileDescription { } impl UnixFileDescription for EventFd { - fn epoll_active_events<'tcx>(&self) -> InterpResult<'tcx, EpollEvents> { + fn epoll_active_events<'tcx>(&self) -> InterpResult<'tcx, EpollReadiness> { // We only check the status of EPOLLIN and EPOLLOUT flags for eventfd. If other event flags // need to be supported in the future, the check should be added here. - interp_ok(EpollEvents { + interp_ok(EpollReadiness { epollin: self.counter.get() != 0, epollout: self.counter.get() != MAX_COUNTER, - ..EpollEvents::new() + ..EpollReadiness::empty() }) } } diff --git a/src/tools/miri/src/shims/unix/mod.rs b/src/tools/miri/src/shims/unix/mod.rs index c55a28bfa7b2..cae31d47c1b5 100644 --- a/src/tools/miri/src/shims/unix/mod.rs +++ b/src/tools/miri/src/shims/unix/mod.rs @@ -5,6 +5,7 @@ mod fs; mod mem; mod socket; +mod socket_address; mod sync; mod thread; mod virtual_socket; @@ -20,9 +21,12 @@ pub use self::env::{EvalContextExt as _, UnixEnvVars}; pub use self::fd::{EvalContextExt as _, UnixFileDescription}; pub use self::fs::{DirTable, EvalContextExt as _}; -pub use self::linux_like::epoll::EpollInterestTable; +pub use self::linux_like::epoll::{ + Epoll, EpollInterestTable, EvalContextExt as EpollEvalContextExt, +}; pub use self::mem::EvalContextExt as _; pub use self::socket::EvalContextExt as _; +pub use self::socket_address::EvalContextExt as _; pub use self::sync::EvalContextExt as _; pub use self::thread::{EvalContextExt as _, ThreadNameResult}; pub use self::virtual_socket::EvalContextExt as _; diff --git a/src/tools/miri/src/shims/unix/socket.rs b/src/tools/miri/src/shims/unix/socket.rs index 99a6378704af..853e69c23411 100644 --- a/src/tools/miri/src/shims/unix/socket.rs +++ b/src/tools/miri/src/shims/unix/socket.rs @@ -1,10 +1,10 @@ -use std::cell::{Cell, RefCell}; +use std::cell::{Cell, RefCell, RefMut}; +use std::io; use std::io::Read; -use std::net::{Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr, SocketAddrV4, SocketAddrV6}; +use std::net::{Ipv4Addr, Shutdown, SocketAddr, SocketAddrV4}; +use std::sync::atomic::AtomicBool; use std::time::Duration; -use std::{io, iter}; -use mio::Interest; use mio::event::Source; use mio::net::{TcpListener, TcpStream}; use rustc_abi::Size; @@ -12,9 +12,10 @@ use rustc_middle::throw_unsup_format; use rustc_target::spec::Os; -use crate::concurrency::blocking_io::InterestReceiver; use crate::shims::files::{EvalContextExt as _, FdId, FileDescription, FileDescriptionRef}; use crate::shims::unix::UnixFileDescription; +use crate::shims::unix::linux_like::epoll::{EpollReadiness, EvalContextExt as _}; +use crate::shims::unix::socket_address::EvalContextExt as _; use crate::*; #[derive(Debug, PartialEq)] @@ -56,6 +57,10 @@ struct Socket { state: RefCell, /// Whether this fd is non-blocking or not. is_non_block: Cell, + /// The current blocking I/O readiness of the file description. + io_readiness: RefCell, + /// [`Some`] when the socket had an async error which has not yet been fetched via `SO_ERROR`. + error: RefCell>, } impl FileDescription for Socket { @@ -65,12 +70,21 @@ fn name(&self) -> &'static str { fn destroy<'tcx>( self, - _self_id: FdId, + self_id: FdId, communicate_allowed: bool, - _ecx: &mut MiriInterpCx<'tcx>, - ) -> InterpResult<'tcx, std::io::Result<()>> { + ecx: &mut MiriInterpCx<'tcx>, + ) -> InterpResult<'tcx, io::Result<()>> { assert!(communicate_allowed, "cannot have `Socket` with isolation enabled!"); + if matches!( + &*self.state.borrow(), + SocketState::Listening(_) | SocketState::Connecting(_) | SocketState::Connected(_) + ) { + // There exists an associated host socket so we need to deregister it + // from the blocking I/O manager. + ecx.machine.blocking_io.deregister(self_id, self) + }; + interp_ok(Ok(())) } @@ -112,8 +126,7 @@ fn read<'tcx>( } else { // The socket is in blocking mode and thus the read call should block // until we can read some bytes from the socket. - this.block_for_recv(socket, ptr, len, /* should_peek */ false, finish); - interp_ok(()) + this.block_for_recv(socket, ptr, len, /* should_peek */ false, finish) } } ), @@ -158,8 +171,7 @@ fn write<'tcx>( } else { // The socket is in blocking mode and thus the write call should block // until we can write some bytes into the socket. - this.block_for_send(socket, ptr, len, finish); - interp_ok(()) + this.block_for_send(socket, ptr, len, finish) } } ), @@ -167,12 +179,10 @@ fn write<'tcx>( } fn short_fd_operations(&self) -> bool { - // Linux de-facto guarantees (or at least, applications like tokio assume [1, 2]) that - // when a read/write on a streaming socket comes back short, the kernel buffer is - // empty/full. SO we can't do short reads/writes here. - // - // [1]: https://github.com/tokio-rs/tokio/blob/6c03e03898d71eca976ee1ad8481cf112ae722ba/tokio/src/io/poll_evented.rs#L182 - // [2]: https://github.com/tokio-rs/tokio/blob/6c03e03898d71eca976ee1ad8481cf112ae722ba/tokio/src/io/poll_evented.rs#L240 + // Linux guarantees that when a read/write on a streaming socket comes back short, + // the kernel buffer is empty/full: + // See in Q&A section. + // So we can't do short reads/writes here. false } @@ -251,6 +261,10 @@ fn ioctl<'tcx>( throw_unsup_format!("ioctl: unsupported operation {op:#x} on socket"); } + + fn epoll_active_events<'tcx>(&self) -> InterpResult<'tcx, EpollReadiness> { + interp_ok(EpollReadiness::from(&*self.io_readiness.borrow())) + } } impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} @@ -328,6 +342,8 @@ fn socket( family, state: RefCell::new(SocketState::Initial), is_non_block: Cell::new(is_sock_nonblock), + io_readiness: RefCell::new(BlockingIoSourceReadiness::empty()), + error: RefCell::new(None), }); interp_ok(Scalar::from_i32(fds.insert(fd))) @@ -342,7 +358,7 @@ fn bind( let this = self.eval_context_mut(); let socket = this.read_scalar(socket)?.to_i32()?; - let address = match this.socket_address(address, address_len, "bind")? { + let address = match this.read_socket_address(address, address_len, "bind")? { Ok(addr) => addr, Err(e) => return this.set_last_error_and_return_i32(e), }; @@ -425,7 +441,13 @@ fn listen(&mut self, socket: &OpTy<'tcx>, backlog: &OpTy<'tcx>) -> InterpResult< match *state { SocketState::Bound(socket_addr) => match TcpListener::bind(socket_addr) { - Ok(listener) => *state = SocketState::Listening(listener), + Ok(listener) => { + *state = SocketState::Listening(listener); + drop(state); + // Register the socket to the blocking I/O manager because + // we now have an associated host socket. + this.machine.blocking_io.register(socket); + } Err(e) => return this.set_last_error_and_return_i32(e), }, SocketState::Initial => { @@ -537,8 +559,7 @@ fn accept4( address_len_ptr, is_client_sock_nonblock, dest.clone(), - ); - interp_ok(()) + ) } } @@ -553,7 +574,7 @@ fn connect( let this = self.eval_context_mut(); let socket = this.read_scalar(socket)?.to_i32()?; - let address = match this.socket_address(address, address_len, "connect")? { + let address = match this.read_socket_address(address, address_len, "connect")? { Ok(address) => address, Err(e) => return this.set_last_error_and_return(e, dest), }; @@ -591,7 +612,12 @@ fn connect( // don't return an error after receiving an [`Interest::WRITEABLE`] // event on the stream. match TcpStream::connect(address) { - Ok(stream) => *socket.state.borrow_mut() = SocketState::Connecting(stream), + Ok(stream) => { + *socket.state.borrow_mut() = SocketState::Connecting(stream); + // Register the socket to the blocking I/O manager because + // we now have an associated host socket. + this.machine.blocking_io.register(socket.clone()); + } Err(e) => return this.set_last_error_and_return(e, dest), }; @@ -730,8 +756,7 @@ fn send( Err(e) => this.set_last_error_and_return(e, &dest) } }), - ); - interp_ok(()) + ) } } ), @@ -853,8 +878,7 @@ fn recv( Err(e) => this.set_last_error_and_return(e, &dest) } }), - ); - interp_ok(()) + ) } } ), @@ -930,6 +954,152 @@ fn setsockopt( ); } + fn getsockopt( + &mut self, + socket: &OpTy<'tcx>, + level: &OpTy<'tcx>, + option_name: &OpTy<'tcx>, + option_value: &OpTy<'tcx>, + option_len: &OpTy<'tcx>, + ) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + + 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()?; + // These two pointers are used to return the value: `len_ptr` initially stores how much space + // is available. If the actual value fits into that space, it is written to + // `value_ptr` and `len_ptr` is updated to represent how many bytes + // were actually written. If the value does not fit, it is silently truncated. + // Also see . + let option_value_ptr = this.read_pointer(option_value)?; + let option_len_ptr = this.read_pointer(option_len)?; + + // Get the file handle + let Some(fd) = this.machine.fds.get(socket) else { + return this.set_last_error_and_return_i32(LibcError("EBADF")); + }; + + let Some(socket) = fd.downcast::() else { + // Man page specifies to return ENOTSOCK if `fd` is not a socket. + return this.set_last_error_and_return_i32(LibcError("ENOTSOCK")); + }; + + if option_value_ptr == Pointer::null() || option_len_ptr == Pointer::null() { + // This socket option returns a value and thus we need to return EFAULT + // when either the value or the length pointers are null pointers. + return this.set_last_error_and_return_i32(LibcError("EFAULT")); + } + + let socklen_layout = this.libc_ty_layout("socklen_t"); + let option_len_ptr_mplace = this.ptr_to_mplace(option_len_ptr, socklen_layout); + let option_len: usize = this + .read_scalar(&option_len_ptr_mplace)? + .to_int(socklen_layout.size)? + .try_into() + .unwrap(); + + // We need a temporary buffer as `option_value_ptr` might not point to a large enough + // buffer, in which case we have to truncate. + let value_buffer = if level == this.eval_libc_i32("SOL_SOCKET") { + let opt_so_error = this.eval_libc_i32("SO_ERROR"); + + 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()), + }; + // Clear our own stored error -- it was either `take`n above or it is outdated. + socket.error.replace(None); + + // We know there is no longer an async error and thus we need to update the + // I/O and epoll readiness of the socket. + 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)?; + value_buffer + } else { + throw_unsup_format!( + "getsockopt: 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 { + let ttl = match &*socket.state.borrow() { + SocketState::Initial | SocketState::Bound(_) => + throw_unsup_format!( + "getsockopt: reading option IP_TTL on level IPPROTO_IP is only supported \ + on connected and listening sockets" + ), + SocketState::Listening(listener) => listener.ttl(), + SocketState::Connecting(stream) | SocketState::Connected(stream) => + stream.ttl(), + }; + + let ttl = match ttl { + Ok(ttl) => ttl, + Err(e) => return this.set_last_error_and_return_i32(e), + }; + + // Allocate new buffer on the stack with the `u32` layout. + let value_buffer = this.allocate(this.machine.layouts.u32, MemoryKind::Stack)?; + this.write_int(ttl, &value_buffer)?; + value_buffer + } else { + throw_unsup_format!( + "getsockopt: option {option_name:#x} is unsupported for level IPPROTO_IP", + ); + } + } else { + throw_unsup_format!( + "getsockopt: level {level:#x} is unsupported, only SOL_SOCKET is allowed" + ) + }; + + // Truncated size of the output value. + let output_value_len = value_buffer.layout.size.min(Size::from_bytes(option_len)); + // Copy the truncated value into the buffer pointed to by `option_value_ptr`. + this.mem_copy( + value_buffer.ptr(), + option_value_ptr, + // Truncate the value to fit the provided buffer. + output_value_len, + // The buffers are guaranteed to not overlap since the `value_buffer` + // was just newly allocated on the stack. + true, + )?; + // Deallocate the value buffer as it was only needed to store the value and + // copy it into the buffer pointed to by `option_value_ptr`. + this.deallocate_ptr(value_buffer.ptr(), None, MemoryKind::Stack)?; + + // On output, the length pointer contains the amount of bytes written -- not the size + // of the value before truncation. + this.write_scalar( + Scalar::from_uint(output_value_len.bytes(), socklen_layout.size), + &option_len_ptr_mplace, + )?; + + interp_ok(Scalar::from_i32(0)) + } + fn getsockname( &mut self, socket: &OpTy<'tcx>, @@ -975,15 +1145,31 @@ fn getsockname( Ok(address) => address, Err(e) => return this.set_last_error_and_return_i32(e), }, + SocketState::Connecting(stream) | SocketState::Connected(stream) => { + if cfg!(windows) && matches!(&*state, SocketState::Connecting(_)) { + // FIXME: On Windows hosts `TcpStream::local_addr` returns `0.0.0.0:0` whilst + // the socket is connecting: + // + // This is problematic because UNIX targets could expect a real local address even + // for a connecting non-blocking socket. + + static DEDUP: AtomicBool = AtomicBool::new(false); + if !DEDUP.swap(true, std::sync::atomic::Ordering::Relaxed) { + this.emit_diagnostic(NonHaltingDiagnostic::ConnectingSocketGetsockname); + } + } + match stream.local_addr() { + Ok(address) => address, + Err(e) => return this.set_last_error_and_return_i32(e), + } + } // 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. - _ => SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0)), + SocketState::Initial => SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0)), }; - match this.write_socket_address(&address, address_ptr, address_len_ptr, "getsockname")? { - Ok(_) => interp_ok(Scalar::from_i32(0)), - Err(e) => this.set_last_error_and_return_i32(e), - } + this.write_socket_address(&address, address_ptr, address_len_ptr, "getsockname") + .map(|_| Scalar::from_i32(0)) } fn getpeername( @@ -1040,15 +1226,13 @@ fn getpeername( Err(e) => return this.set_last_error_and_return(e, &dest), }; - match this.write_socket_address( + this.write_socket_address( &address, address_ptr, address_len_ptr, "getpeername", - )? { - Ok(_) => this.write_scalar(Scalar::from_i32(0), &dest), - Err(e) => this.set_last_error_and_return(e, &dest), - } + )?; + this.write_scalar(Scalar::from_i32(0), &dest) } ), ) @@ -1099,274 +1283,6 @@ fn shutdown(&mut self, socket: &OpTy<'tcx>, how: &OpTy<'tcx>) -> InterpResult<'t impl<'tcx> EvalContextPrivExt<'tcx> for crate::MiriInterpCx<'tcx> {} trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { - /// Attempt to turn an address and length operand into a standard library socket address. - /// - /// Returns an IO error should the address length not match the address family length. - fn socket_address( - &self, - address: &OpTy<'tcx>, - address_len: &OpTy<'tcx>, - foreign_name: &'static str, - ) -> InterpResult<'tcx, Result> { - let this = self.eval_context_ref(); - - let socklen_layout = this.libc_ty_layout("socklen_t"); - // We only support address lengths which can be stored in a u64 since the - // size of a layout is also stored in a u64. - let address_len: u64 = - this.read_scalar(address_len)?.to_int(socklen_layout.size)?.try_into().unwrap(); - - // Initially, treat address as generic sockaddr just to extract the family field. - let sockaddr_layout = this.libc_ty_layout("sockaddr"); - if address_len < sockaddr_layout.size.bytes() { - // Address length should be at least as big as the generic sockaddr - return interp_ok(Err(LibcError("EINVAL"))); - } - let address = this.deref_pointer_as(address, sockaddr_layout)?; - - let family_field = this.project_field_named(&address, "sa_family")?; - let family_layout = this.libc_ty_layout("sa_family_t"); - let family = this.read_scalar(&family_field)?.to_int(family_layout.size)?; - - // Depending on the family, decide whether it's IPv4 or IPv6 and use specialized layout - // to extract address and port. - let socket_addr = if family == this.eval_libc_i32("AF_INET").into() { - let sockaddr_in_layout = this.libc_ty_layout("sockaddr_in"); - if address_len != sockaddr_in_layout.size.bytes() { - // Address length should be exactly the length of an IPv4 address. - return interp_ok(Err(LibcError("EINVAL"))); - } - let address = address.transmute(sockaddr_in_layout, this)?; - - let port_field = this.project_field_named(&address, "sin_port")?; - // Read bytes and treat them as big endian since port is stored in network byte order. - let port_bytes: [u8; 2] = this - .read_bytes_ptr_strip_provenance(port_field.ptr(), Size::from_bytes(2))? - .try_into() - .unwrap(); - let port = u16::from_be_bytes(port_bytes); - - let addr_field = this.project_field_named(&address, "sin_addr")?; - let s_addr_field = this.project_field_named(&addr_field, "s_addr")?; - // Read bytes and treat them as big endian since address is stored in network byte order. - let addr_bytes: [u8; 4] = this - .read_bytes_ptr_strip_provenance(s_addr_field.ptr(), Size::from_bytes(4))? - .try_into() - .unwrap(); - let addr_bits = u32::from_be_bytes(addr_bytes); - - SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::from_bits(addr_bits), port)) - } else if family == this.eval_libc_i32("AF_INET6").into() { - let sockaddr_in6_layout = this.libc_ty_layout("sockaddr_in6"); - if address_len != sockaddr_in6_layout.size.bytes() { - // Address length should be exactly the length of an IPv6 address. - return interp_ok(Err(LibcError("EINVAL"))); - } - // We cannot transmute since the `sockaddr_in6` layout is bigger than the `sockaddr` layout. - let address = address.offset(Size::ZERO, sockaddr_in6_layout, this)?; - - let port_field = this.project_field_named(&address, "sin6_port")?; - // Read bytes and treat them as big endian since port is stored in network byte order. - let port_bytes: [u8; 2] = this - .read_bytes_ptr_strip_provenance(port_field.ptr(), Size::from_bytes(2))? - .try_into() - .unwrap(); - let port = u16::from_be_bytes(port_bytes); - - let addr_field = this.project_field_named(&address, "sin6_addr")?; - let s_addr_field = this - .project_field_named(&addr_field, "s6_addr")? - .transmute(this.machine.layouts.u128, this)?; - // Read bytes and treat them as big endian since address is stored in network byte order. - let addr_bytes: [u8; 16] = this - .read_bytes_ptr_strip_provenance(s_addr_field.ptr(), Size::from_bytes(16))? - .try_into() - .unwrap(); - let addr_bits = u128::from_be_bytes(addr_bytes); - - let flowinfo_field = this.project_field_named(&address, "sin6_flowinfo")?; - // flowinfo doesn't get the big endian treatment as this field is stored in native byte order - // and not in network byte order. - let flowinfo = this.read_scalar(&flowinfo_field)?.to_u32()?; - - let scope_id_field = this.project_field_named(&address, "sin6_scope_id")?; - // scope_id doesn't get the big endian treatment as this field is stored in native byte order - // and not in network byte order. - let scope_id = this.read_scalar(&scope_id_field)?.to_u32()?; - - SocketAddr::V6(SocketAddrV6::new( - Ipv6Addr::from_bits(addr_bits), - port, - flowinfo, - scope_id, - )) - } else { - // Socket of other types shouldn't be created in a first place and - // thus also no address family of another type should be supported. - throw_unsup_format!( - "{foreign_name}: address family {family:#x} is unsupported, \ - only AF_INET and AF_INET6 are allowed" - ); - }; - - interp_ok(Ok(socket_addr)) - } - - /// Attempt to write a standard library socket address into a pointer. - /// - /// The `address_len_ptr` parameter serves both as input and output parameter. - /// On input, it points to the size of the buffer `address_ptr` points to, and - /// on output it points to the non-truncated size of the written address in the - /// buffer pointed to by `address_ptr`. - /// - /// If the address buffer doesn't fit the whole address, the address is truncated to not - /// overflow the buffer. - fn write_socket_address( - &mut self, - address: &SocketAddr, - address_ptr: Pointer, - address_len_ptr: Pointer, - foreign_name: &'static str, - ) -> InterpResult<'tcx, Result<(), IoError>> { - let this = self.eval_context_mut(); - - if address_ptr == Pointer::null() || address_len_ptr == Pointer::null() { - // The POSIX man page doesn't account for the cases where the `address_ptr` or - // `address_len_ptr` could be null pointers. Thus, this behavior is undefined! - throw_ub_format!( - "{foreign_name}: writing a socket address but the address or the length pointer is a null pointer" - ) - } - - let socklen_layout = this.libc_ty_layout("socklen_t"); - let address_buffer_len_place = this.ptr_to_mplace(address_len_ptr, socklen_layout); - // We only support buffer lengths which can be stored in a u64 since the - // size of a layout in bytes is also stored in a u64. - let address_buffer_len: u64 = this - .read_scalar(&address_buffer_len_place)? - .to_int(socklen_layout.size)? - .try_into() - .unwrap(); - - let (address_buffer, address_layout) = match address { - SocketAddr::V4(address) => { - // IPv4 address bytes; already stored in network byte order. - let address_bytes = address.ip().octets(); - // Port needs to be manually turned into network byte order. - let port = address.port().to_be(); - - let sockaddr_in_layout = this.libc_ty_layout("sockaddr_in"); - // Allocate new buffer on the stack with the `sockaddr_in` layout. - // We need a temporary buffer as `address_ptr` might not point to a large enough - // buffer, in which case we have to truncate. - let address_buffer = this.allocate(sockaddr_in_layout, MemoryKind::Stack)?; - // Zero the whole buffer as some libc targets have additional fields which we fill - // with zero bytes (just like the standard library does it). - this.write_bytes_ptr( - address_buffer.ptr(), - iter::repeat_n(0, address_buffer.layout.size.bytes_usize()), - )?; - - let sin_family_field = this.project_field_named(&address_buffer, "sin_family")?; - // We cannot simply write the `AF_INET` scalar into the `sin_family_field` because on most - // systems the field has a layout of 16-bit whilst the scalar has a size of 32-bit. - // Since the `AF_INET` constant is chosen such that it can safely be converted into - // a 16-bit integer, we use the following logic to get a scalar of the right size. - let af_inet = this.eval_libc("AF_INET"); - let address_family = - Scalar::from_int(af_inet.to_int(af_inet.size())?, sin_family_field.layout.size); - this.write_scalar(address_family, &sin_family_field)?; - - let sin_port_field = this.project_field_named(&address_buffer, "sin_port")?; - // Write the port in target native endianness bytes as we already converted it - // to big endian above. - this.write_bytes_ptr(sin_port_field.ptr(), port.to_ne_bytes())?; - - let sin_addr_field = this.project_field_named(&address_buffer, "sin_addr")?; - let s_addr_field = this.project_field_named(&sin_addr_field, "s_addr")?; - this.write_bytes_ptr(s_addr_field.ptr(), address_bytes)?; - - (address_buffer, sockaddr_in_layout) - } - SocketAddr::V6(address) => { - // IPv6 address bytes; already stored in network byte order. - let address_bytes = address.ip().octets(); - // Port needs to be manually turned into network byte order. - let port = address.port().to_be(); - // Flowinfo is stored in native byte order. - let flowinfo = address.flowinfo(); - // Scope id is stored in native byte order. - let scope_id = address.scope_id(); - - let sockaddr_in6_layout = this.libc_ty_layout("sockaddr_in6"); - // Allocate new buffer on the stack with the `sockaddr_in6` layout. - // We need a temporary buffer as `address_ptr` might not point to a large enough - // buffer, in which case we have to truncate. - let address_buffer = this.allocate(sockaddr_in6_layout, MemoryKind::Stack)?; - // Zero the whole buffer as some libc targets have additional fields which we fill - // with zero bytes (just like the standard library does it). - this.write_bytes_ptr( - address_buffer.ptr(), - iter::repeat_n(0, address_buffer.layout.size.bytes_usize()), - )?; - - let sin6_family_field = this.project_field_named(&address_buffer, "sin6_family")?; - // We cannot simply write the `AF_INET6` scalar into the `sin6_family_field` because on most - // systems the field has a layout of 16-bit whilst the scalar has a size of 32-bit. - // Since the `AF_INET6` constant is chosen such that it can safely be converted into - // a 16-bit integer, we use the following logic to get a scalar of the right size. - let af_inet6 = this.eval_libc("AF_INET6"); - let address_family = Scalar::from_int( - af_inet6.to_int(af_inet6.size())?, - sin6_family_field.layout.size, - ); - this.write_scalar(address_family, &sin6_family_field)?; - - let sin6_port_field = this.project_field_named(&address_buffer, "sin6_port")?; - // Write the port in target native endianness bytes as we already converted it - // to big endian above. - this.write_bytes_ptr(sin6_port_field.ptr(), port.to_ne_bytes())?; - - let sin6_flowinfo_field = - this.project_field_named(&address_buffer, "sin6_flowinfo")?; - this.write_scalar(Scalar::from_u32(flowinfo), &sin6_flowinfo_field)?; - - let sin6_scope_id_field = - this.project_field_named(&address_buffer, "sin6_scope_id")?; - this.write_scalar(Scalar::from_u32(scope_id), &sin6_scope_id_field)?; - - let sin6_addr_field = this.project_field_named(&address_buffer, "sin6_addr")?; - let s6_addr_field = this.project_field_named(&sin6_addr_field, "s6_addr")?; - this.write_bytes_ptr(s6_addr_field.ptr(), address_bytes)?; - - (address_buffer, sockaddr_in6_layout) - } - }; - - // Copy the truncated address into the pointer pointed to by `address_ptr`. - this.mem_copy( - address_buffer.ptr(), - address_ptr, - // Truncate the address to fit the provided buffer. - address_layout.size.min(Size::from_bytes(address_buffer_len)), - // The buffers are guaranteed to not overlap since the `address_buffer` - // was just newly allocated on the stack. - true, - )?; - // Deallocate the address buffer as it was only needed to construct the address and - // copy it into the buffer pointed to by `address_ptr`. - this.deallocate_ptr(address_buffer.ptr(), None, MemoryKind::Stack)?; - // Size of the non-truncated address. - let address_len = address_layout.size.bytes(); - - this.write_scalar( - Scalar::from_uint(address_len, socklen_layout.size), - &address_buffer_len_place, - )?; - - interp_ok(Ok(())) - } - /// Block the thread until there's an incoming connection or an error occurred. /// /// This recursively calls itself should the operation still block for some reason. @@ -1380,11 +1296,11 @@ fn block_for_accept( address_len_ptr: Pointer, is_client_sock_nonblock: bool, dest: MPlaceTy<'tcx>, - ) { + ) -> InterpResult<'tcx> { let this = self.eval_context_mut(); this.block_thread_for_io( socket.clone(), - Interest::READABLE, + BlockingIoInterest::Read, None, callback!(@capture<'tcx> { address_ptr: Pointer, @@ -1395,6 +1311,9 @@ fn block_for_accept( } |this, kind: UnblockKind| { assert_eq!(kind, UnblockKind::Ready); + // Remove the blocking I/O interest for unblocking this thread. + this.machine.blocking_io.remove_blocked_thread(socket.id(), this.machine.threads.active_thread()); + match this.try_non_block_accept(&socket, address_ptr, address_len_ptr, is_client_sock_nonblock)? { Ok(sockfd) => { // We need to create the scalar using the destination size since @@ -1405,13 +1324,12 @@ fn block_for_accept( }, Err(IoError::HostError(e)) if e.kind() == io::ErrorKind::WouldBlock => { // We need to block the thread again as it would still block. - this.block_for_accept(socket, address_ptr, address_len_ptr, is_client_sock_nonblock, dest); - interp_ok(()) + this.block_for_accept(socket, address_ptr, address_len_ptr, is_client_sock_nonblock, dest) } Err(e) => this.set_last_error_and_return(e, &dest), } }), - ); + ) } /// Attempt to accept an incoming connection on the listening socket in a @@ -1437,6 +1355,13 @@ fn try_non_block_accept( let (stream, addr) = match listener.accept() { Ok(peer) => peer, + Err(e) if e.kind() == io::ErrorKind::WouldBlock => { + // We know that the source is not readable so we need to update its readiness. + socket.io_readiness.borrow_mut().readable = false; + this.update_epoll_active_events(socket.clone(), /* force_edge */ false)?; + + return interp_ok(Err(IoError::HostError(e))); + } Err(e) => return interp_ok(Err(IoError::HostError(e))), }; @@ -1449,18 +1374,19 @@ fn try_non_block_accept( // We only attempt a write if the address pointer is not a null pointer. // If the address pointer is a null pointer the user isn't interested in the // address and we don't need to write anything. - if let Err(e) = - this.write_socket_address(&addr, address_ptr, address_len_ptr, "accept4")? - { - return interp_ok(Err(e)); - }; + this.write_socket_address(&addr, address_ptr, address_len_ptr, "accept4")?; } let fd = this.machine.fds.new_ref(Socket { family, state: RefCell::new(SocketState::Connected(stream)), is_non_block: Cell::new(is_client_sock_nonblock), + io_readiness: RefCell::new(BlockingIoSourceReadiness::empty()), + error: RefCell::new(None), }); + // Register the socket to the blocking I/O manager because + // there is an associated host socket. + this.machine.blocking_io.register(fd.clone()); let sockfd = this.machine.fds.insert(fd); interp_ok(Ok(sockfd)) } @@ -1478,11 +1404,11 @@ fn block_for_send( buffer_ptr: Pointer, length: usize, finish: DynMachineCallback<'tcx, Result>, - ) { + ) -> InterpResult<'tcx> { let this = self.eval_context_mut(); this.block_thread_for_io( socket.clone(), - Interest::WRITABLE, + BlockingIoInterest::Write, None, callback!(@capture<'tcx> { socket: FileDescriptionRef, @@ -1492,15 +1418,18 @@ fn block_for_send( } |this, kind: UnblockKind| { assert_eq!(kind, UnblockKind::Ready); + // Remove the blocking I/O interest for unblocking this thread. + this.machine.blocking_io.remove_blocked_thread(socket.id(), this.machine.threads.active_thread()); + match this.try_non_block_send(&socket, buffer_ptr, length)? { Err(IoError::HostError(e)) if e.kind() == io::ErrorKind::WouldBlock => { - this.block_for_send(socket, buffer_ptr, length, finish); - interp_ok(()) + // We need to block the thread again as it would still block. + this.block_for_send(socket, buffer_ptr, length, finish) }, result => finish.call(this, result) } }), - ); + ) } /// Attempt to send bytes into the connected socket in a non-blocking manner. @@ -1524,20 +1453,17 @@ fn try_non_block_send( // FIXME: When the host does a short write, we should emit an epoll edge -- at least for targets for which tokio assumes no short writes: // match result { - Err(IoError::HostError(e)) if e.kind() == io::ErrorKind::NotConnected => { + Err(IoError::HostError(e)) + if matches!(e.kind(), io::ErrorKind::NotConnected | io::ErrorKind::WouldBlock) => + { + // We know that the source is not writable so we need to update it's readiness. + socket.io_readiness.borrow_mut().writable = false; + this.update_epoll_active_events(socket.clone(), /* force_edge */ false)?; + // On Windows hosts, `send` can return WSAENOTCONN where EAGAIN or EWOULDBLOCK // would be returned on UNIX-like systems. We thus remap this error to an EWOULDBLOCK. interp_ok(Err(IoError::HostError(io::ErrorKind::WouldBlock.into()))) } - Err(IoError::HostError(e)) - if cfg!(windows) - && matches!(e.raw_os_error(), Some(/* WSAESHUTDOWN error code */ 10058)) => - { - // FIXME: This is a temporary workaround for handling WSAESHUTDOWN errors - // on Windows. A discussion on how those errors should be handled can be found here: - // - interp_ok(Err(IoError::HostError(io::ErrorKind::BrokenPipe.into()))) - } result => interp_ok(result), } } @@ -1556,11 +1482,11 @@ fn block_for_recv( length: usize, should_peek: bool, finish: DynMachineCallback<'tcx, Result>, - ) { + ) -> InterpResult<'tcx> { let this = self.eval_context_mut(); this.block_thread_for_io( socket.clone(), - Interest::READABLE, + BlockingIoInterest::Read, None, callback!(@capture<'tcx> { socket: FileDescriptionRef, @@ -1571,16 +1497,18 @@ fn block_for_recv( } |this, kind: UnblockKind| { assert_eq!(kind, UnblockKind::Ready); + // Remove the blocking I/O interest for unblocking this thread. + this.machine.blocking_io.remove_blocked_thread(socket.id(), this.machine.threads.active_thread()); + match this.try_non_block_recv(&socket, buffer_ptr, length, should_peek)? { Err(IoError::HostError(e)) if e.kind() == io::ErrorKind::WouldBlock => { // We need to block the thread again as it would still block. - this.block_for_recv(socket, buffer_ptr, length, should_peek, finish); - interp_ok(()) + this.block_for_recv(socket, buffer_ptr, length, should_peek, finish) }, result => finish.call(this, result) } }), - ); + ) } /// Attempt to receive bytes from the connected socket in a non-blocking manner. @@ -1611,7 +1539,13 @@ fn try_non_block_recv( // FIXME: When the host does a short read, we should emit an epoll edge -- at least for targets for which tokio assumes no short reads: // match result { - Err(IoError::HostError(e)) if e.kind() == io::ErrorKind::NotConnected => { + Err(IoError::HostError(e)) + if matches!(e.kind(), io::ErrorKind::NotConnected | io::ErrorKind::WouldBlock) => + { + // We know that the source is not readable so we need to update it's readiness. + socket.io_readiness.borrow_mut().readable = false; + this.update_epoll_active_events(socket.clone(), /* force_edge */ false)?; + // On Windows hosts, `recv` can return WSAENOTCONN where EAGAIN or EWOULDBLOCK // would be returned on UNIX-like systems. We thus remap this error to an EWOULDBLOCK. interp_ok(Err(IoError::HostError(io::ErrorKind::WouldBlock.into()))) @@ -1666,7 +1600,7 @@ fn ensure_connected( this.block_thread_for_io( socket.clone(), - Interest::WRITABLE, + BlockingIoInterest::Write, timeout, callback!( @capture<'tcx> { @@ -1675,11 +1609,13 @@ fn ensure_connected( foreign_name: &'static str, action: DynMachineCallback<'tcx, Result<(), ()>>, } |this, kind: UnblockKind| { + // Remove the blocking I/O interest for unblocking this thread. + this.machine.blocking_io.remove_blocked_thread(socket.id(), this.machine.threads.active_thread()); + if UnblockKind::TimedOut == kind { // We can only time out when `should_wait` is false. // This then means that the socket is not yet connected. assert!(!should_wait); - this.machine.blocking_io.deregister(socket.id(), InterestReceiver::UnblockThread(this.active_thread())); return action.call(this, Err(())) } @@ -1705,17 +1641,18 @@ fn ensure_connected( }; // Manually check whether there were any errors since calling `connect`. - if let Ok(Some(_)) = stream.take_error() { + 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. - // + + // 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)); + // 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. - // - // FIXME: We're currently just dropping the error information. Eventually - // we'll have to store it so that it can be recovered by the user. *state = SocketState::Initial; drop(state); return action.call(this, Err(())) @@ -1752,9 +1689,7 @@ fn ensure_connected( action.call(this, Ok(())) } ), - ); - - interp_ok(()) + ) } } @@ -1764,7 +1699,7 @@ impl VisitProvenance for FileDescriptionRef { fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {} } -impl WithSource for Socket { +impl SourceFileDescription for Socket { fn with_source(&self, f: &mut dyn FnMut(&mut dyn Source) -> io::Result<()>) -> io::Result<()> { let mut state = self.state.borrow_mut(); match &mut *state { @@ -1774,4 +1709,8 @@ fn with_source(&self, f: &mut dyn FnMut(&mut dyn Source) -> io::Result<()>) -> i _ => unreachable!(), } } + + fn get_readiness_mut(&self) -> RefMut<'_, BlockingIoSourceReadiness> { + self.io_readiness.borrow_mut() + } } diff --git a/src/tools/miri/src/shims/unix/socket_address.rs b/src/tools/miri/src/shims/unix/socket_address.rs new file mode 100644 index 000000000000..90c316d0d116 --- /dev/null +++ b/src/tools/miri/src/shims/unix/socket_address.rs @@ -0,0 +1,499 @@ +use std::iter; +use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs}; + +use rustc_abi::Size; +use rustc_target::spec::Env; + +use crate::*; + +impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} +pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { + fn getaddrinfo( + &mut self, + node: &OpTy<'tcx>, + service: &OpTy<'tcx>, + hints: &OpTy<'tcx>, + res: &OpTy<'tcx>, + ) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + + let node_ptr = this.read_pointer(node)?; + let service_ptr = this.read_pointer(service)?; + let hints_ptr = this.read_pointer(hints)?; + let res_mplace = this.deref_pointer(res)?; + + if node_ptr == Pointer::null() { + // We cannot get an address without the `node` part because + // the [`ToSocketAddrs`] trait requires an address. + throw_unsup_format!( + "getaddrinfo: getting the address info without a `node` is unsupported" + ); + } + + let mut port = 0; + if service_ptr != Pointer::null() { + // The C-string at `service_ptr` is either a port number or a name of a + // well-known service. + let service_c_str = this.read_c_str(service_ptr)?; + + // Try to parse `service_c_str` as a number -- the only case we support. + match str::from_utf8(service_c_str).ok().and_then(|s| s.parse::().ok()) { + Some(service_port) => port = service_port, + None => { + // The string is not a valid port number; this is unsupported + // because the standard library's [`ToSocketAddrs`] only supports + // numeric ports. + throw_unsup_format!( + "getaddrinfo: non-numeric `service` arguments aren't supported" + ) + } + } + } + + let node_c_str = this.read_c_str(node_ptr)?; + let Some(node_str) = str::from_utf8(node_c_str).ok() else { + throw_unsup_format!("getaddrinfo: node is not a valid UTF-8 string") + }; + + if hints_ptr == Pointer::null() { + // The standard library only supports getting TCP address information. The + // empty hints pointer would allow any socket type so we cannot support it. + throw_unsup_format!( + "getaddrinfo: getting address info without providing socket type hint is unsupported" + ) + } + + let hints_layout = this.libc_ty_layout("addrinfo"); + let hints_mplace = this.ptr_to_mplace(hints_ptr, hints_layout); + + let family_field = this.project_field_named(&hints_mplace, "ai_family")?; + let family = this.read_scalar(&family_field)?; + if family != Scalar::from_i32(0) { + // We cannot provide a family hint to the standard library implementation. + throw_unsup_format!("getaddrinfo: family hints are not supported") + } + + let socktype_field = this.project_field_named(&hints_mplace, "ai_socktype")?; + let socktype = this.read_scalar(&socktype_field)?; + if socktype != this.eval_libc("SOCK_STREAM") { + // The standard library only supports getting TCP address information. + throw_unsup_format!( + "getaddrinfo: only queries with socket type SOCK_STREAM are supported" + ) + } + + let protocol_field = this.project_field_named(&hints_mplace, "ai_protocol")?; + let protocol = this.read_scalar(&protocol_field)?; + if protocol != Scalar::from_i32(0) { + // We cannot provide a protocol hint to the standard library implementation. + throw_unsup_format!("getaddrinfo: protocol hints are not supported") + } + + let flags_field = this.project_field_named(&hints_mplace, "ai_flags")?; + let flags = this.read_scalar(&flags_field)?; + if flags != Scalar::from_i32(0) { + // We cannot provide any flag hints to the standard library implementation. + throw_unsup_format!("getaddrinfo: flag hints are not supported") + } + + let socket_addrs = match (node_str, port).to_socket_addrs() { + Ok(addrs) => addrs, + Err(e) => { + // `getaddrinfo` returns negative integer values when there was an error during socket + // address resolution. Because the standard library doesn't expose those integer values + // directly, we just return a generic protocol error. + // The actual error is emitted as part of a warning diagnostic. + this.emit_diagnostic(NonHaltingDiagnostic::SocketAddressResolution { error: e }); + this.set_last_error(LibcError("EPROTO"))?; + return interp_ok(this.eval_libc("EAI_SYSTEM")); + } + }; + + let res_ptr = this.allocate_address_infos(socket_addrs)?; + + this.write_pointer(res_ptr, &res_mplace)?; + interp_ok(Scalar::from_i32(0)) + } + + fn freeaddrinfo(&mut self, res: &OpTy<'tcx>) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + + let res_ptr = this.read_pointer(res)?; + + this.free_address_infos(res_ptr) + } + + /// Attempt to turn an address and length operand into a standard library socket address. + /// + /// Returns an IO error should the address length not match the address family length. + fn read_socket_address( + &self, + address: &OpTy<'tcx>, + address_len: &OpTy<'tcx>, + foreign_name: &'static str, + ) -> InterpResult<'tcx, Result> { + let this = self.eval_context_ref(); + + let socklen_layout = this.libc_ty_layout("socklen_t"); + // We only support address lengths which can be stored in a u64 since the + // size of a layout is also stored in a u64. + let address_len: u64 = + this.read_scalar(address_len)?.to_int(socklen_layout.size)?.try_into().unwrap(); + + // Initially, treat address as generic sockaddr just to extract the family field. + let sockaddr_layout = this.libc_ty_layout("sockaddr"); + if address_len < sockaddr_layout.size.bytes() { + // Address length should be at least as big as the generic sockaddr + return interp_ok(Err(LibcError("EINVAL"))); + } + let address = this.deref_pointer_as(address, sockaddr_layout)?; + + let family_field = this.project_field_named(&address, "sa_family")?; + let family_layout = this.libc_ty_layout("sa_family_t"); + let family = this.read_scalar(&family_field)?.to_int(family_layout.size)?; + + // Depending on the family, decide whether it's IPv4 or IPv6 and use specialized layout + // to extract address and port. + let socket_addr = if family == this.eval_libc_i32("AF_INET").into() { + let sockaddr_in_layout = this.libc_ty_layout("sockaddr_in"); + if address_len != sockaddr_in_layout.size.bytes() { + // Address length should be exactly the length of an IPv4 address. + return interp_ok(Err(LibcError("EINVAL"))); + } + let address = address.transmute(sockaddr_in_layout, this)?; + + let port_field = this.project_field_named(&address, "sin_port")?; + // Read bytes and treat them as big endian since port is stored in network byte order. + let port_bytes: [u8; 2] = this + .read_bytes_ptr_strip_provenance(port_field.ptr(), Size::from_bytes(2))? + .try_into() + .unwrap(); + let port = u16::from_be_bytes(port_bytes); + + let addr_field = this.project_field_named(&address, "sin_addr")?; + let s_addr_field = this.project_field_named(&addr_field, "s_addr")?; + // Read bytes and treat them as big endian since address is stored in network byte order. + let addr_bytes: [u8; 4] = this + .read_bytes_ptr_strip_provenance(s_addr_field.ptr(), Size::from_bytes(4))? + .try_into() + .unwrap(); + let addr_bits = u32::from_be_bytes(addr_bytes); + + SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::from_bits(addr_bits), port)) + } else if family == this.eval_libc_i32("AF_INET6").into() { + let sockaddr_in6_layout = this.libc_ty_layout("sockaddr_in6"); + if address_len != sockaddr_in6_layout.size.bytes() { + // Address length should be exactly the length of an IPv6 address. + return interp_ok(Err(LibcError("EINVAL"))); + } + // We cannot transmute since the `sockaddr_in6` layout is bigger than the `sockaddr` layout. + let address = address.offset(Size::ZERO, sockaddr_in6_layout, this)?; + + let port_field = this.project_field_named(&address, "sin6_port")?; + // Read bytes and treat them as big endian since port is stored in network byte order. + let port_bytes: [u8; 2] = this + .read_bytes_ptr_strip_provenance(port_field.ptr(), Size::from_bytes(2))? + .try_into() + .unwrap(); + let port = u16::from_be_bytes(port_bytes); + + let addr_field = this.project_field_named(&address, "sin6_addr")?; + let s_addr_field = this + .project_field_named(&addr_field, "s6_addr")? + .transmute(this.machine.layouts.u128, this)?; + // Read bytes and treat them as big endian since address is stored in network byte order. + let addr_bytes: [u8; 16] = this + .read_bytes_ptr_strip_provenance(s_addr_field.ptr(), Size::from_bytes(16))? + .try_into() + .unwrap(); + let addr_bits = u128::from_be_bytes(addr_bytes); + + let flowinfo_field = this.project_field_named(&address, "sin6_flowinfo")?; + // flowinfo doesn't get the big endian treatment as this field is stored in native byte order + // and not in network byte order. + let flowinfo = this.read_scalar(&flowinfo_field)?.to_u32()?; + + let scope_id_field = this.project_field_named(&address, "sin6_scope_id")?; + // scope_id doesn't get the big endian treatment as this field is stored in native byte order + // and not in network byte order. + let scope_id = this.read_scalar(&scope_id_field)?.to_u32()?; + + SocketAddr::V6(SocketAddrV6::new( + Ipv6Addr::from_bits(addr_bits), + port, + flowinfo, + scope_id, + )) + } else { + // Socket of other types shouldn't be created in a first place and + // thus also no address family of another type should be supported. + throw_unsup_format!( + "{foreign_name}: address family {family:#x} is unsupported, \ + only AF_INET and AF_INET6 are allowed" + ); + }; + + interp_ok(Ok(socket_addr)) + } + + /// Attempt to write a standard library socket address into a pointer. + /// + /// The `address_len_ptr` parameter serves both as input and output parameter. + /// On input, it points to the size of the buffer `address_ptr` points to, and + /// on output it points to the non-truncated size of the written address in the + /// buffer pointed to by `address_ptr`. + /// + /// If the address buffer doesn't fit the whole address, the address is truncated to not + /// overflow the buffer. + fn write_socket_address( + &mut self, + address: &SocketAddr, + address_ptr: Pointer, + address_len_ptr: Pointer, + foreign_name: &'static str, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + + if address_ptr == Pointer::null() || address_len_ptr == Pointer::null() { + // The POSIX man page doesn't account for the cases where the `address_ptr` or + // `address_len_ptr` could be null pointers. Thus, this behavior is undefined! + throw_ub_format!( + "{foreign_name}: writing a socket address but the address or the length pointer is a null pointer" + ) + } + + let socklen_layout = this.libc_ty_layout("socklen_t"); + let address_buffer_len_place = this.ptr_to_mplace(address_len_ptr, socklen_layout); + // We only support buffer lengths which can be stored in a u64 since the + // size of a layout in bytes is also stored in a u64. + let address_buffer_len: u64 = this + .read_scalar(&address_buffer_len_place)? + .to_int(socklen_layout.size)? + .try_into() + .unwrap(); + + let address_buffer = match address { + SocketAddr::V4(address) => { + // IPv4 address bytes; already stored in network byte order. + let address_bytes = address.ip().octets(); + // Port needs to be manually turned into network byte order. + let port = address.port().to_be(); + + let sockaddr_in_layout = this.libc_ty_layout("sockaddr_in"); + // Allocate new buffer on the stack with the `sockaddr_in` layout. + // We need a temporary buffer as `address_ptr` might not point to a large enough + // buffer, in which case we have to truncate. + let address_buffer = this.allocate(sockaddr_in_layout, MemoryKind::Stack)?; + // Zero the whole buffer as some libc targets have additional fields which we fill + // with zero bytes (just like the standard library does it). + this.write_bytes_ptr( + address_buffer.ptr(), + iter::repeat_n(0, address_buffer.layout.size.bytes_usize()), + )?; + + let sin_family_field = this.project_field_named(&address_buffer, "sin_family")?; + // We cannot simply write the `AF_INET` scalar into the `sin_family_field` because on most + // systems the field has a layout of 16-bit whilst the scalar has a size of 32-bit. + // Since the `AF_INET` constant is chosen such that it can safely be converted into + // a 16-bit integer, we use the following logic to get a scalar of the right size. + let af_inet = this.eval_libc("AF_INET"); + let address_family = + Scalar::from_int(af_inet.to_int(af_inet.size())?, sin_family_field.layout.size); + this.write_scalar(address_family, &sin_family_field)?; + + let sin_port_field = this.project_field_named(&address_buffer, "sin_port")?; + // Write the port in target native endianness bytes as we already converted it + // to big endian above. + this.write_bytes_ptr(sin_port_field.ptr(), port.to_ne_bytes())?; + + let sin_addr_field = this.project_field_named(&address_buffer, "sin_addr")?; + let s_addr_field = this.project_field_named(&sin_addr_field, "s_addr")?; + this.write_bytes_ptr(s_addr_field.ptr(), address_bytes)?; + + address_buffer + } + SocketAddr::V6(address) => { + // IPv6 address bytes; already stored in network byte order. + let address_bytes = address.ip().octets(); + // Port needs to be manually turned into network byte order. + let port = address.port().to_be(); + // Flowinfo is stored in native byte order. + let flowinfo = address.flowinfo(); + // Scope id is stored in native byte order. + let scope_id = address.scope_id(); + + let sockaddr_in6_layout = this.libc_ty_layout("sockaddr_in6"); + // Allocate new buffer on the stack with the `sockaddr_in6` layout. + // We need a temporary buffer as `address_ptr` might not point to a large enough + // buffer, in which case we have to truncate. + let address_buffer = this.allocate(sockaddr_in6_layout, MemoryKind::Stack)?; + // Zero the whole buffer as some libc targets have additional fields which we fill + // with zero bytes (just like the standard library does it). + this.write_bytes_ptr( + address_buffer.ptr(), + iter::repeat_n(0, address_buffer.layout.size.bytes_usize()), + )?; + + let sin6_family_field = this.project_field_named(&address_buffer, "sin6_family")?; + // We cannot simply write the `AF_INET6` scalar into the `sin6_family_field` because on most + // systems the field has a layout of 16-bit whilst the scalar has a size of 32-bit. + // Since the `AF_INET6` constant is chosen such that it can safely be converted into + // a 16-bit integer, we use the following logic to get a scalar of the right size. + let af_inet6 = this.eval_libc("AF_INET6"); + let address_family = Scalar::from_int( + af_inet6.to_int(af_inet6.size())?, + sin6_family_field.layout.size, + ); + this.write_scalar(address_family, &sin6_family_field)?; + + let sin6_port_field = this.project_field_named(&address_buffer, "sin6_port")?; + // Write the port in target native endianness bytes as we already converted it + // to big endian above. + this.write_bytes_ptr(sin6_port_field.ptr(), port.to_ne_bytes())?; + + let sin6_flowinfo_field = + this.project_field_named(&address_buffer, "sin6_flowinfo")?; + this.write_scalar(Scalar::from_u32(flowinfo), &sin6_flowinfo_field)?; + + let sin6_scope_id_field = + this.project_field_named(&address_buffer, "sin6_scope_id")?; + this.write_scalar(Scalar::from_u32(scope_id), &sin6_scope_id_field)?; + + let sin6_addr_field = this.project_field_named(&address_buffer, "sin6_addr")?; + let s6_addr_field = this.project_field_named(&sin6_addr_field, "s6_addr")?; + this.write_bytes_ptr(s6_addr_field.ptr(), address_bytes)?; + + address_buffer + } + }; + + // Copy the truncated address into the pointer pointed to by `address_ptr`. + this.mem_copy( + address_buffer.ptr(), + address_ptr, + // Truncate the address to fit the provided buffer. + address_buffer.layout.size.min(Size::from_bytes(address_buffer_len)), + // The buffers are guaranteed to not overlap since the `address_buffer` + // was just newly allocated on the stack. + true, + )?; + // Deallocate the address buffer as it was only needed to construct the address and + // copy it into the buffer pointed to by `address_ptr`. + this.deallocate_ptr(address_buffer.ptr(), None, MemoryKind::Stack)?; + // Size of the non-truncated address. + let address_len = address_buffer.layout.size.bytes(); + + this.write_scalar( + Scalar::from_uint(address_len, socklen_layout.size), + &address_buffer_len_place, + )?; + + interp_ok(()) + } +} + +impl<'tcx> EvalContextPrivExt<'tcx> for crate::MiriInterpCx<'tcx> {} +trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { + /// Allocate a linked list of address info structs from an iterator of [`SocketAddr`]s. + /// Returns a pointer pointing to the head of the linked list. + fn allocate_address_infos( + &mut self, + mut addresses: impl Iterator, + ) -> InterpResult<'tcx, Pointer> { + let this = self.eval_context_mut(); + + let Some(address) = addresses.next() else { + // Iterator is empty; we return a null pointer. + return interp_ok(Pointer::null()); + }; + + let addrinfo_layout = this.libc_ty_layout("addrinfo"); + + let addrinfo_mplace = + this.allocate(addrinfo_layout, MiriMemoryKind::SocketAddress.into())?; + + let flags_mplace = this.project_field_named(&addrinfo_mplace, "ai_flags")?; + // We don't support flag hints and depending on the target libc we have different default values: + // "According to POSIX.1, specifying hints as NULL should cause `ai_flags` to be assumed as 0. + // The GNU C library instead assumes a value of (AI_V4MAPPED | AI_ADDRCONFIG) for this case, + // since this value is considered an improvement on the specification." + let flags = if matches!(this.tcx.sess.target.env, Env::Gnu) { + this.eval_libc_i32("AI_V4MAPPED") | this.eval_libc_i32("AI_ADDRCONFIG") + } else { + 0 + }; + this.write_int(flags, &flags_mplace)?; + + let family_mplace = this.project_field_named(&addrinfo_mplace, "ai_family")?; + let family = match &address { + SocketAddr::V4(_) => this.eval_libc("AF_INET"), + SocketAddr::V6(_) => this.eval_libc("AF_INET6"), + }; + this.write_scalar(family, &family_mplace)?; + + let socktype_mplace = this.project_field_named(&addrinfo_mplace, "ai_socktype")?; + this.write_scalar(this.eval_libc("SOCK_STREAM"), &socktype_mplace)?; + + let protocol_mplace = this.project_field_named(&addrinfo_mplace, "ai_protocol")?; + // We don't support protocol hints and thus we just return zero which falls back + // to the default protocol for the provided socket type. + this.write_int(0, &protocol_mplace)?; + + // `sockaddr_storage` is guaranteed to fit any `sockaddr_*` address structure. + let sockaddr_layout = this.libc_ty_layout("sockaddr_storage"); + + let addrlen_mplace = this.project_field_named(&addrinfo_mplace, "ai_addrlen")?; + let addr_mplace = this.project_field_named(&addrinfo_mplace, "ai_addr")?; + this.write_int(sockaddr_layout.size.bytes(), &addrlen_mplace)?; + + let sockaddr_mplace = this.allocate(sockaddr_layout, MiriMemoryKind::Machine.into())?; + // Zero the newly allocated socket address struct. + this.write_bytes_ptr( + sockaddr_mplace.ptr(), + iter::repeat_n(0, sockaddr_mplace.layout.size.bytes_usize()), + )?; + this.write_socket_address( + &address, + sockaddr_mplace.ptr(), + addrlen_mplace.ptr(), + "getaddrinfo", + )?; + this.write_pointer(sockaddr_mplace.ptr(), &addr_mplace)?; + + let canonname_mplace = this.project_field_named(&addrinfo_mplace, "ai_canonname")?; + this.write_pointer(Pointer::null(), &canonname_mplace)?; + + // Allocate remaining list and store a pointer to it. + let next_mplace = this.project_field_named(&addrinfo_mplace, "ai_next")?; + let next_ptr = this.allocate_address_infos(addresses)?; + this.write_pointer(next_ptr, &next_mplace)?; + + interp_ok(addrinfo_mplace.ptr()) + } + + /// Deallocate the linked list of address info structs. + /// `address_ptr` points to the start from where we deallocate recursively. + fn free_address_infos(&mut self, address_ptr: Pointer) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + + if address_ptr == Pointer::null() { + // We're at the end of the linked list. + return interp_ok(()); + } + + let addrinfo_layout = this.libc_ty_layout("addrinfo"); + let addrinfo_mplace = this.ptr_to_mplace(address_ptr, addrinfo_layout); + + let addr_field = this.project_field_named(&addrinfo_mplace, "ai_addr")?; + let addr_ptr = this.read_pointer(&addr_field)?; + this.deallocate_ptr(addr_ptr, None, MiriMemoryKind::Machine.into())?; + + let next_field = this.project_field_named(&addrinfo_mplace, "ai_next")?; + let next_ptr = this.read_pointer(&next_field)?; + this.free_address_infos(next_ptr)?; + + this.deallocate_ptr(address_ptr, None, MiriMemoryKind::SocketAddress.into())?; + + interp_ok(()) + } +} diff --git a/src/tools/miri/src/shims/unix/solarish/foreign_items.rs b/src/tools/miri/src/shims/unix/solarish/foreign_items.rs index d4ebdeab86f1..37b665ceebd1 100644 --- a/src/tools/miri/src/shims/unix/solarish/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/solarish/foreign_items.rs @@ -122,7 +122,6 @@ fn emulate_foreign_item_inner( let result = this.socket(domain, type_, protocol)?; this.write_scalar(result, dest)?; } - "__xnet_bind" => { let [socket, address, address_len] = this.check_shim_sig( shim_sig!(extern "C" fn(i32, *const _, libc::socklen_t) -> i32), @@ -142,6 +141,27 @@ fn emulate_foreign_item_inner( )?; this.connect(socket, address, address_len, dest)?; } + "__xnet_getaddrinfo" => { + let [node, service, hints, res] = this.check_shim_sig( + shim_sig!(extern "C" fn(*const _, *const _, *const _, *mut _) -> i32), + link_name, + abi, + args, + )?; + let result = this.getaddrinfo(node, service, hints, res)?; + this.write_scalar(result, dest)?; + } + "__xnet_getsockopt" => { + let [socket, level, option_name, option_value, option_len] = this.check_shim_sig( + shim_sig!(extern "C" fn(i32, i32, i32, *mut _, *mut _) -> i32), + link_name, + abi, + args, + )?; + let result = + this.getsockopt(socket, level, option_name, option_value, option_len)?; + this.write_scalar(result, dest)?; + } // Miscellaneous "___errno" => { diff --git a/src/tools/miri/src/shims/unix/virtual_socket.rs b/src/tools/miri/src/shims/unix/virtual_socket.rs index 51bd30840ffb..7fc8556406db 100644 --- a/src/tools/miri/src/shims/unix/virtual_socket.rs +++ b/src/tools/miri/src/shims/unix/virtual_socket.rs @@ -13,7 +13,7 @@ EvalContextExt as _, FdId, FileDescription, FileDescriptionRef, WeakFileDescriptionRef, }; use crate::shims::unix::UnixFileDescription; -use crate::shims::unix::linux_like::epoll::{EpollEvents, EvalContextExt as _}; +use crate::shims::unix::linux_like::epoll::{EpollReadiness, EvalContextExt as _}; use crate::*; /// The maximum capacity of the socketpair buffer in bytes. @@ -136,12 +136,10 @@ fn write<'tcx>( } fn short_fd_operations(&self) -> bool { - // Linux de-facto guarantees (or at least, applications like tokio assume [1, 2]) that - // when a read/write on a streaming socket comes back short, the kernel buffer is - // empty/full. SO we can't do short reads/writes here. - // - // [1]: https://github.com/tokio-rs/tokio/blob/6c03e03898d71eca976ee1ad8481cf112ae722ba/tokio/src/io/poll_evented.rs#L182 - // [2]: https://github.com/tokio-rs/tokio/blob/6c03e03898d71eca976ee1ad8481cf112ae722ba/tokio/src/io/poll_evented.rs#L240 + // Linux guarantees that when a read/write on a streaming socket comes back short, + // the kernel buffer is empty/full: + // See in Q&A section. + // So we can't do short reads/writes here. false } @@ -392,20 +390,20 @@ fn virtual_socket_read<'tcx>( } impl UnixFileDescription for VirtualSocket { - fn epoll_active_events<'tcx>(&self) -> InterpResult<'tcx, EpollEvents> { + fn epoll_active_events<'tcx>(&self) -> InterpResult<'tcx, EpollReadiness> { // We only check the status of EPOLLIN, EPOLLOUT, EPOLLHUP and EPOLLRDHUP flags. // If other event flags need to be supported in the future, the check should be added here. - let mut epoll_ready_events = EpollEvents::new(); + let mut epoll_readiness = EpollReadiness::empty(); // Check if it is readable. if let Some(readbuf) = &self.readbuf { if !readbuf.borrow().buf.is_empty() { - epoll_ready_events.epollin = true; + epoll_readiness.epollin = true; } } else { // Without a read buffer, reading never blocks, so we are always ready. - epoll_ready_events.epollin = true; + epoll_readiness.epollin = true; } // Check if is writable. @@ -414,28 +412,28 @@ fn epoll_active_events<'tcx>(&self) -> InterpResult<'tcx, EpollEvents> { let data_size = writebuf.borrow().buf.len(); let available_space = MAX_SOCKETPAIR_BUFFER_CAPACITY.strict_sub(data_size); if available_space != 0 { - epoll_ready_events.epollout = true; + epoll_readiness.epollout = true; } } else { // Without a write buffer, writing never blocks. - epoll_ready_events.epollout = true; + epoll_readiness.epollout = true; } } else { // Peer FD has been closed. This always sets both the RDHUP and HUP flags // as we do not support `shutdown` that could be used to partially close the stream. - epoll_ready_events.epollrdhup = true; - epoll_ready_events.epollhup = true; + epoll_readiness.epollrdhup = true; + epoll_readiness.epollhup = true; // Since the peer is closed, even if no data is available reads will return EOF and // writes will return EPIPE. In other words, they won't block, so we mark this as ready // for read and write. - epoll_ready_events.epollin = true; - epoll_ready_events.epollout = true; + epoll_readiness.epollin = true; + epoll_readiness.epollout = true; // If there is data lost in peer_fd, set EPOLLERR. if self.peer_lost_data.get() { - epoll_ready_events.epollerr = true; + epoll_readiness.epollerr = true; } } - interp_ok(epoll_ready_events) + interp_ok(epoll_readiness) } } diff --git a/src/tools/miri/test-cargo-miri/Cargo.toml b/src/tools/miri/test-cargo-miri/Cargo.toml index 3f08f802cf42..45168dff155d 100644 --- a/src/tools/miri/test-cargo-miri/Cargo.toml +++ b/src/tools/miri/test-cargo-miri/Cargo.toml @@ -5,7 +5,6 @@ exclude = ["no-std-smoke"] # it wants to be panic="abort" [package] name = "cargo-miri-test" version = "0.1.0" -authors = ["Miri Team"] edition = "2024" [dependencies] diff --git a/src/tools/miri/test-cargo-miri/exported-symbol-dep/Cargo.toml b/src/tools/miri/test-cargo-miri/exported-symbol-dep/Cargo.toml index 00c41172c3af..70b7ea29ab87 100644 --- a/src/tools/miri/test-cargo-miri/exported-symbol-dep/Cargo.toml +++ b/src/tools/miri/test-cargo-miri/exported-symbol-dep/Cargo.toml @@ -1,5 +1,4 @@ [package] name = "exported_symbol_dep" version = "0.1.0" -authors = ["Miri Team"] edition = "2018" diff --git a/src/tools/miri/test-cargo-miri/exported-symbol/Cargo.toml b/src/tools/miri/test-cargo-miri/exported-symbol/Cargo.toml index 7c01be1a85f9..69a74e988b23 100644 --- a/src/tools/miri/test-cargo-miri/exported-symbol/Cargo.toml +++ b/src/tools/miri/test-cargo-miri/exported-symbol/Cargo.toml @@ -1,7 +1,6 @@ [package] name = "exported_symbol" version = "0.1.0" -authors = ["Miri Team"] edition = "2018" [dependencies] diff --git a/src/tools/miri/test-cargo-miri/issue-1567/Cargo.toml b/src/tools/miri/test-cargo-miri/issue-1567/Cargo.toml index 6a6e09036a01..f502df0e342b 100644 --- a/src/tools/miri/test-cargo-miri/issue-1567/Cargo.toml +++ b/src/tools/miri/test-cargo-miri/issue-1567/Cargo.toml @@ -1,7 +1,6 @@ [package] name = "issue_1567" version = "0.1.0" -authors = ["Miri Team"] edition = "2018" [lib] diff --git a/src/tools/miri/test-cargo-miri/issue-1691/Cargo.toml b/src/tools/miri/test-cargo-miri/issue-1691/Cargo.toml index 3100cc6a60b5..2ba1cee14273 100644 --- a/src/tools/miri/test-cargo-miri/issue-1691/Cargo.toml +++ b/src/tools/miri/test-cargo-miri/issue-1691/Cargo.toml @@ -1,7 +1,6 @@ [package] name = "issue_1691" version = "0.1.0" -authors = ["Miri Team"] edition = "2018" [lib] diff --git a/src/tools/miri/test-cargo-miri/issue-1705/Cargo.toml b/src/tools/miri/test-cargo-miri/issue-1705/Cargo.toml index ae63647a8881..d11f4433fd49 100644 --- a/src/tools/miri/test-cargo-miri/issue-1705/Cargo.toml +++ b/src/tools/miri/test-cargo-miri/issue-1705/Cargo.toml @@ -1,7 +1,6 @@ [package] name = "issue_1705" version = "0.1.0" -authors = ["Miri Team"] edition = "2018" [lib] diff --git a/src/tools/miri/test-cargo-miri/issue-rust-86261/Cargo.toml b/src/tools/miri/test-cargo-miri/issue-rust-86261/Cargo.toml index a6b65ebb5318..39077ac7b634 100644 --- a/src/tools/miri/test-cargo-miri/issue-rust-86261/Cargo.toml +++ b/src/tools/miri/test-cargo-miri/issue-rust-86261/Cargo.toml @@ -1,5 +1,4 @@ [package] name = "issue_rust_86261" version = "0.1.0" -authors = ["Miri Team"] edition = "2018" diff --git a/src/tools/miri/test-cargo-miri/proc-macro-crate/Cargo.toml b/src/tools/miri/test-cargo-miri/proc-macro-crate/Cargo.toml index 89652f9b0428..f1dc4acb6dff 100644 --- a/src/tools/miri/test-cargo-miri/proc-macro-crate/Cargo.toml +++ b/src/tools/miri/test-cargo-miri/proc-macro-crate/Cargo.toml @@ -2,7 +2,6 @@ # regression test for issue 1760 name = "proc_macro_crate" version = "0.1.0" -authors = ["Miri Team"] edition = "2018" [lib] diff --git a/src/tools/miri/test-cargo-miri/subcrate/Cargo.toml b/src/tools/miri/test-cargo-miri/subcrate/Cargo.toml index f2f6360f2d21..396e54488489 100644 --- a/src/tools/miri/test-cargo-miri/subcrate/Cargo.toml +++ b/src/tools/miri/test-cargo-miri/subcrate/Cargo.toml @@ -1,7 +1,6 @@ [package] name = "subcrate" version = "0.1.0" -authors = ["Miri Team"] # This is deliberately *not* on the 2024 edition to ensure doctests keep working # on old editions. edition = "2018" diff --git a/src/tools/miri/tests/deps/Cargo.toml b/src/tools/miri/tests/deps/Cargo.toml index 73b3025212ec..a4d06b628081 100644 --- a/src/tools/miri/tests/deps/Cargo.toml +++ b/src/tools/miri/tests/deps/Cargo.toml @@ -1,5 +1,4 @@ [package] -authors = ["Miri Team"] description = "dependencies that unit tests can have" license = "MIT OR Apache-2.0" name = "miri-test-deps" diff --git a/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_mutex_deadlock.stderr b/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_mutex_deadlock.stderr index 468c0e63cc34..31a25b17546c 100644 --- a/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_mutex_deadlock.stderr +++ b/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_mutex_deadlock.stderr @@ -8,9 +8,9 @@ LL | let ret = unsafe { libc::pthread_join(id, ptr::null_mut()) }; = note: stack backtrace: 0: std::sys::thread::PLATFORM::Thread::join at RUSTLIB/std/src/sys/thread/PLATFORM.rs:LL:CC - 1: std::thread::lifecycle::JoinInner::join + 1: std::thread::lifecycle::JoinInner::<'_, ()>::join at RUSTLIB/std/src/thread/lifecycle.rs:LL:CC - 2: std::thread::JoinHandle::join + 2: std::thread::JoinHandle::<()>::join at RUSTLIB/std/src/thread/join_handle.rs:LL:CC 3: main at tests/fail-dep/concurrency/libc_pthread_mutex_deadlock.rs:LL:CC diff --git a/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_mutex_free_while_queued.stderr b/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_mutex_free_while_queued.stderr index b120a447d8b7..ae37c89406bb 100644 --- a/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_mutex_free_while_queued.stderr +++ b/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_mutex_free_while_queued.stderr @@ -10,9 +10,9 @@ LL | self.1.deallocate(From::from(ptr.cast()), layout); = note: stack backtrace: 0: as std::ops::Drop>::drop at RUSTLIB/alloc/src/boxed.rs:LL:CC - 1: std::ptr::drop_glue)) + 1: std::ptr::drop_glue::> - shim(Some(std::boxed::Box)) at RUSTLIB/core/src/ptr/mod.rs:LL:CC - 2: std::mem::drop + 2: std::mem::drop::> at RUSTLIB/core/src/mem/mod.rs:LL:CC 3: main::{closure#0}::{closure#2} at tests/fail-dep/concurrency/libc_pthread_mutex_free_while_queued.rs:LL:CC diff --git a/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_rwlock_write_read_deadlock.stderr b/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_rwlock_write_read_deadlock.stderr index e00c297a9b29..1cfbbb46a1c3 100644 --- a/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_rwlock_write_read_deadlock.stderr +++ b/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_rwlock_write_read_deadlock.stderr @@ -8,9 +8,9 @@ LL | let ret = unsafe { libc::pthread_join(id, ptr::null_mut()) }; = note: stack backtrace: 0: std::sys::thread::PLATFORM::Thread::join at RUSTLIB/std/src/sys/thread/PLATFORM.rs:LL:CC - 1: std::thread::lifecycle::JoinInner::join + 1: std::thread::lifecycle::JoinInner::<'_, ()>::join at RUSTLIB/std/src/thread/lifecycle.rs:LL:CC - 2: std::thread::JoinHandle::join + 2: std::thread::JoinHandle::<()>::join at RUSTLIB/std/src/thread/join_handle.rs:LL:CC 3: main at tests/fail-dep/concurrency/libc_pthread_rwlock_write_read_deadlock.rs:LL:CC diff --git a/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_rwlock_write_write_deadlock.stderr b/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_rwlock_write_write_deadlock.stderr index 21fa8dcbd18d..2354bdcdbe1d 100644 --- a/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_rwlock_write_write_deadlock.stderr +++ b/src/tools/miri/tests/fail-dep/concurrency/libc_pthread_rwlock_write_write_deadlock.stderr @@ -8,9 +8,9 @@ LL | let ret = unsafe { libc::pthread_join(id, ptr::null_mut()) }; = note: stack backtrace: 0: std::sys::thread::PLATFORM::Thread::join at RUSTLIB/std/src/sys/thread/PLATFORM.rs:LL:CC - 1: std::thread::lifecycle::JoinInner::join + 1: std::thread::lifecycle::JoinInner::<'_, ()>::join at RUSTLIB/std/src/thread/lifecycle.rs:LL:CC - 2: std::thread::JoinHandle::join + 2: std::thread::JoinHandle::<()>::join at RUSTLIB/std/src/thread/join_handle.rs:LL:CC 3: main at tests/fail-dep/concurrency/libc_pthread_rwlock_write_write_deadlock.rs:LL:CC diff --git a/src/tools/miri/tests/fail-dep/concurrency/windows_join_detached.stderr b/src/tools/miri/tests/fail-dep/concurrency/windows_join_detached.stderr index 73e79852d63a..77e3bb78ef10 100644 --- a/src/tools/miri/tests/fail-dep/concurrency/windows_join_detached.stderr +++ b/src/tools/miri/tests/fail-dep/concurrency/windows_join_detached.stderr @@ -10,9 +10,9 @@ LL | let rc = unsafe { c::WaitForSingleObject(self.handle.as_raw_handle( = note: stack backtrace: 0: std::sys::thread::PLATFORM::Thread::join at RUSTLIB/std/src/sys/thread/PLATFORM.rs:LL:CC - 1: std::thread::lifecycle::JoinInner::join + 1: std::thread::lifecycle::JoinInner::<'_, ()>::join at RUSTLIB/std/src/thread/lifecycle.rs:LL:CC - 2: std::thread::JoinHandle::join + 2: std::thread::JoinHandle::<()>::join at RUSTLIB/std/src/thread/join_handle.rs:LL:CC 3: main at tests/fail-dep/concurrency/windows_join_detached.rs:LL:CC diff --git a/src/tools/miri/tests/fail-dep/concurrency/windows_join_main.stderr b/src/tools/miri/tests/fail-dep/concurrency/windows_join_main.stderr index 2f3e41b405bc..a8015e18eb73 100644 --- a/src/tools/miri/tests/fail-dep/concurrency/windows_join_main.stderr +++ b/src/tools/miri/tests/fail-dep/concurrency/windows_join_main.stderr @@ -8,9 +8,9 @@ LL | let rc = unsafe { c::WaitForSingleObject(self.handle.as_raw_handle( = note: stack backtrace: 0: std::sys::thread::PLATFORM::Thread::join at RUSTLIB/std/src/sys/thread/PLATFORM.rs:LL:CC - 1: std::thread::lifecycle::JoinInner::join + 1: std::thread::lifecycle::JoinInner::<'_, ()>::join at RUSTLIB/std/src/thread/lifecycle.rs:LL:CC - 2: std::thread::JoinHandle::join + 2: std::thread::JoinHandle::<()>::join at RUSTLIB/std/src/thread/join_handle.rs:LL:CC 3: main at tests/fail-dep/concurrency/windows_join_main.rs:LL:CC diff --git a/src/tools/miri/tests/fail-dep/concurrency/windows_join_self.stderr b/src/tools/miri/tests/fail-dep/concurrency/windows_join_self.stderr index 8f28324363b3..16290624ca76 100644 --- a/src/tools/miri/tests/fail-dep/concurrency/windows_join_self.stderr +++ b/src/tools/miri/tests/fail-dep/concurrency/windows_join_self.stderr @@ -8,9 +8,9 @@ LL | let rc = unsafe { c::WaitForSingleObject(self.handle.as_raw_handle( = note: stack backtrace: 0: std::sys::thread::PLATFORM::Thread::join at RUSTLIB/std/src/sys/thread/PLATFORM.rs:LL:CC - 1: std::thread::lifecycle::JoinInner::join + 1: std::thread::lifecycle::JoinInner::<'_, ()>::join at RUSTLIB/std/src/thread/lifecycle.rs:LL:CC - 2: std::thread::JoinHandle::join + 2: std::thread::JoinHandle::<()>::join at RUSTLIB/std/src/thread/join_handle.rs:LL:CC 3: main at tests/fail-dep/concurrency/windows_join_self.rs:LL:CC diff --git a/src/tools/miri/tests/fail-dep/libc/eventfd_block_read_twice.stderr b/src/tools/miri/tests/fail-dep/libc/eventfd_block_read_twice.stderr index f5cfd35847af..62e8eb422006 100644 --- a/src/tools/miri/tests/fail-dep/libc/eventfd_block_read_twice.stderr +++ b/src/tools/miri/tests/fail-dep/libc/eventfd_block_read_twice.stderr @@ -8,9 +8,9 @@ LL | let ret = unsafe { libc::pthread_join(id, ptr::null_mut()) }; = note: stack backtrace: 0: std::sys::thread::PLATFORM::Thread::join at RUSTLIB/std/src/sys/thread/PLATFORM.rs:LL:CC - 1: std::thread::lifecycle::JoinInner::join + 1: std::thread::lifecycle::JoinInner::<'_, ()>::join at RUSTLIB/std/src/thread/lifecycle.rs:LL:CC - 2: std::thread::JoinHandle::join + 2: std::thread::JoinHandle::<()>::join at RUSTLIB/std/src/thread/join_handle.rs:LL:CC 3: main at tests/fail-dep/libc/eventfd_block_read_twice.rs:LL:CC diff --git a/src/tools/miri/tests/fail-dep/libc/eventfd_block_write_twice.stderr b/src/tools/miri/tests/fail-dep/libc/eventfd_block_write_twice.stderr index 92c3fc47c4fd..e87eabad398a 100644 --- a/src/tools/miri/tests/fail-dep/libc/eventfd_block_write_twice.stderr +++ b/src/tools/miri/tests/fail-dep/libc/eventfd_block_write_twice.stderr @@ -8,9 +8,9 @@ LL | let ret = unsafe { libc::pthread_join(id, ptr::null_mut()) }; = note: stack backtrace: 0: std::sys::thread::PLATFORM::Thread::join at RUSTLIB/std/src/sys/thread/PLATFORM.rs:LL:CC - 1: std::thread::lifecycle::JoinInner::join + 1: std::thread::lifecycle::JoinInner::<'_, ()>::join at RUSTLIB/std/src/thread/lifecycle.rs:LL:CC - 2: std::thread::JoinHandle::join + 2: std::thread::JoinHandle::<()>::join at RUSTLIB/std/src/thread/join_handle.rs:LL:CC 3: main at tests/fail-dep/libc/eventfd_block_write_twice.rs:LL:CC diff --git a/src/tools/miri/tests/fail-dep/libc/libc-epoll-data-race.rs b/src/tools/miri/tests/fail-dep/libc/libc-epoll-data-race.rs index 3b1217cda126..336f39e0512e 100644 --- a/src/tools/miri/tests/fail-dep/libc/libc-epoll-data-race.rs +++ b/src/tools/miri/tests/fail-dep/libc/libc-epoll-data-race.rs @@ -68,14 +68,12 @@ fn main() { let thread1 = spawn(move || { unsafe { VAL_ONE = 41 }; - let data = "abcde".as_bytes().as_ptr(); - let res = unsafe { libc_utils::write_all(fds_a[0], data as *const libc::c_void, 5) }; - assert_eq!(res, 5); + let data = "abcde".as_bytes(); + libc_utils::write_all(fds_a[0], data).unwrap(); unsafe { VAL_TWO = 51 }; - let res = unsafe { libc_utils::write_all(fds_b[0], data as *const libc::c_void, 5) }; - assert_eq!(res, 5); + libc_utils::write_all(fds_b[0], data).unwrap(); }); thread::yield_now(); diff --git a/src/tools/miri/tests/fail-dep/libc/libc-read-and-uninit-premature-eof.rs b/src/tools/miri/tests/fail-dep/libc/libc-read-and-uninit-premature-eof.rs index e2fd6463a116..4400d52e59d6 100644 --- a/src/tools/miri/tests/fail-dep/libc/libc-read-and-uninit-premature-eof.rs +++ b/src/tools/miri/tests/fail-dep/libc/libc-read-and-uninit-premature-eof.rs @@ -1,7 +1,8 @@ //! We test that if we requested to read 4 bytes, but actually read 3 bytes, //! then 3 bytes (not 4) will be initialized. //@ignore-target: windows # no file system support on Windows -//@compile-flags: -Zmiri-disable-isolation +// Short FD ops can affect the exact error message here +//@compile-flags: -Zmiri-disable-isolation -Zmiri-no-short-fd-operations use std::ffi::CString; use std::fs::remove_file; @@ -21,9 +22,9 @@ fn main() { let fd = libc::open(cpath.as_ptr(), libc::O_RDONLY); assert_ne!(fd, -1); let mut buf: MaybeUninit<[u8; 4]> = std::mem::MaybeUninit::uninit(); - // Read as much as we can from a 3-byte file. - let res = libc_utils::read_all(fd, buf.as_mut_ptr().cast::(), 4); - assert!(res == 3); + // Do a 4-byte read; this can actually read at most 3 bytes. + let res = libc::read(fd, buf.as_mut_ptr().cast::(), 4); + assert!(res <= 3); buf.assume_init(); //~ERROR: encountered uninitialized memory, but expected an integer assert_eq!(libc::close(fd), 0); } diff --git a/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs b/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs index 8c51416f5aa9..16ac2d643c4e 100644 --- a/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs +++ b/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs @@ -79,7 +79,7 @@ fn main() { thread::yield_now(); // Create two events at once. - libc_utils::write_all_from_slice(fd1, &0_u64.to_ne_bytes()).unwrap(); + libc_utils::write_all(fd1, &0_u64.to_ne_bytes()).unwrap(); thread1.join().unwrap(); thread2.join().unwrap(); diff --git a/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.stderr b/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.stderr index af6578357a59..4f0f9daa3e0e 100644 --- a/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.stderr +++ b/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.stderr @@ -8,9 +8,9 @@ LL | let ret = unsafe { libc::pthread_join(id, ptr::null_mut()) }; = note: stack backtrace: 0: std::sys::thread::PLATFORM::Thread::join at RUSTLIB/std/src/sys/thread/PLATFORM.rs:LL:CC - 1: std::thread::lifecycle::JoinInner::join + 1: std::thread::lifecycle::JoinInner::<'_, ()>::join at RUSTLIB/std/src/thread/lifecycle.rs:LL:CC - 2: std::thread::JoinHandle::join + 2: std::thread::JoinHandle::<()>::join at RUSTLIB/std/src/thread/join_handle.rs:LL:CC 3: main at tests/fail-dep/libc/libc_epoll_block_two_thread.rs:LL:CC diff --git a/src/tools/miri/tests/fail-dep/libc/socketpair-close-while-blocked.stderr b/src/tools/miri/tests/fail-dep/libc/socketpair-close-while-blocked.stderr index b40c35d6d266..886183be7413 100644 --- a/src/tools/miri/tests/fail-dep/libc/socketpair-close-while-blocked.stderr +++ b/src/tools/miri/tests/fail-dep/libc/socketpair-close-while-blocked.stderr @@ -8,9 +8,9 @@ LL | let ret = unsafe { libc::pthread_join(id, ptr::null_mut()) }; = note: stack backtrace: 0: std::sys::thread::PLATFORM::Thread::join at RUSTLIB/std/src/sys/thread/PLATFORM.rs:LL:CC - 1: std::thread::lifecycle::JoinInner::join + 1: std::thread::lifecycle::JoinInner::<'_, ()>::join at RUSTLIB/std/src/thread/lifecycle.rs:LL:CC - 2: std::thread::JoinHandle::join + 2: std::thread::JoinHandle::<()>::join at RUSTLIB/std/src/thread/join_handle.rs:LL:CC 3: main at tests/fail-dep/libc/socketpair-close-while-blocked.rs:LL:CC diff --git a/src/tools/miri/tests/fail-dep/libc/socketpair_block_read_twice.stderr b/src/tools/miri/tests/fail-dep/libc/socketpair_block_read_twice.stderr index d649076374f5..9c047878d401 100644 --- a/src/tools/miri/tests/fail-dep/libc/socketpair_block_read_twice.stderr +++ b/src/tools/miri/tests/fail-dep/libc/socketpair_block_read_twice.stderr @@ -8,9 +8,9 @@ LL | let ret = unsafe { libc::pthread_join(id, ptr::null_mut()) }; = note: stack backtrace: 0: std::sys::thread::PLATFORM::Thread::join at RUSTLIB/std/src/sys/thread/PLATFORM.rs:LL:CC - 1: std::thread::lifecycle::JoinInner::join + 1: std::thread::lifecycle::JoinInner::<'_, ()>::join at RUSTLIB/std/src/thread/lifecycle.rs:LL:CC - 2: std::thread::JoinHandle::join + 2: std::thread::JoinHandle::<()>::join at RUSTLIB/std/src/thread/join_handle.rs:LL:CC 3: main at tests/fail-dep/libc/socketpair_block_read_twice.rs:LL:CC diff --git a/src/tools/miri/tests/fail-dep/libc/socketpair_block_write_twice.rs b/src/tools/miri/tests/fail-dep/libc/socketpair_block_write_twice.rs index 34eab8aaa046..2f8b5be0c0c5 100644 --- a/src/tools/miri/tests/fail-dep/libc/socketpair_block_write_twice.rs +++ b/src/tools/miri/tests/fail-dep/libc/socketpair_block_write_twice.rs @@ -23,9 +23,7 @@ fn main() { assert_eq!(res, 0); let arr1: [u8; 212992] = [1; 212992]; // Exhaust the space in the buffer so the subsequent write will block. - let res = - unsafe { libc_utils::write_all(fds[0], arr1.as_ptr() as *const libc::c_void, 212992) }; - assert_eq!(res, 212992); + libc_utils::write_all(fds[0], &arr1).unwrap(); let thread1 = thread::spawn(move || { let data = "a".as_bytes(); // The write below will be blocked because the buffer is already full. diff --git a/src/tools/miri/tests/fail-dep/libc/socketpair_block_write_twice.stderr b/src/tools/miri/tests/fail-dep/libc/socketpair_block_write_twice.stderr index 66f3359a1292..b39addc3ce7b 100644 --- a/src/tools/miri/tests/fail-dep/libc/socketpair_block_write_twice.stderr +++ b/src/tools/miri/tests/fail-dep/libc/socketpair_block_write_twice.stderr @@ -8,9 +8,9 @@ LL | let ret = unsafe { libc::pthread_join(id, ptr::null_mut()) }; = note: stack backtrace: 0: std::sys::thread::PLATFORM::Thread::join at RUSTLIB/std/src/sys/thread/PLATFORM.rs:LL:CC - 1: std::thread::lifecycle::JoinInner::join + 1: std::thread::lifecycle::JoinInner::<'_, ()>::join at RUSTLIB/std/src/thread/lifecycle.rs:LL:CC - 2: std::thread::JoinHandle::join + 2: std::thread::JoinHandle::<()>::join at RUSTLIB/std/src/thread/join_handle.rs:LL:CC 3: main at tests/fail-dep/libc/socketpair_block_write_twice.rs:LL:CC diff --git a/src/tools/miri/tests/fail/alloc/alloc_error_handler.stderr b/src/tools/miri/tests/fail/alloc/alloc_error_handler.stderr index ed9b7ad18b0f..e99c0fc99bd9 100644 --- a/src/tools/miri/tests/fail/alloc/alloc_error_handler.stderr +++ b/src/tools/miri/tests/fail/alloc/alloc_error_handler.stderr @@ -10,7 +10,7 @@ LL | crate::process::abort() = note: stack backtrace: 0: std::alloc::rust_oom::{closure#0} at RUSTLIB/std/src/alloc.rs:LL:CC - 1: std::sys::backtrace::__rust_end_short_backtrace + 1: std::sys::backtrace::__rust_end_short_backtrace::<{closure@std::alloc::rust_oom::{closure#0}}, !> at RUSTLIB/std/src/sys/backtrace.rs:LL:CC 2: std::alloc::rust_oom at RUSTLIB/std/src/alloc.rs:LL:CC diff --git a/src/tools/miri/tests/fail/alloc/global_system_mixup.stderr b/src/tools/miri/tests/fail/alloc/global_system_mixup.stderr index 4214e3e7675b..1e9d859b5cf8 100644 --- a/src/tools/miri/tests/fail/alloc/global_system_mixup.stderr +++ b/src/tools/miri/tests/fail/alloc/global_system_mixup.stderr @@ -7,7 +7,7 @@ LL | FREE(); = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information = note: stack backtrace: - 0: std::sys::alloc::PLATFORM::dealloc + 0: std::sys::alloc::PLATFORM::::dealloc at RUSTLIB/std/src/sys/alloc/PLATFORM.rs:LL:CC 1: ::deallocate at RUSTLIB/std/src/alloc.rs:LL:CC diff --git a/src/tools/miri/tests/fail/alloc/stack_free.stderr b/src/tools/miri/tests/fail/alloc/stack_free.stderr index 043c4a680f27..70fc61964df5 100644 --- a/src/tools/miri/tests/fail/alloc/stack_free.stderr +++ b/src/tools/miri/tests/fail/alloc/stack_free.stderr @@ -9,9 +9,9 @@ LL | self.1.deallocate(From::from(ptr.cast()), layout); = note: stack backtrace: 0: as std::ops::Drop>::drop at RUSTLIB/alloc/src/boxed.rs:LL:CC - 1: std::ptr::drop_glue)) + 1: std::ptr::drop_glue::> - shim(Some(std::boxed::Box)) at RUSTLIB/core/src/ptr/mod.rs:LL:CC - 2: std::mem::drop + 2: std::mem::drop::> at RUSTLIB/core/src/mem/mod.rs:LL:CC 3: main at tests/fail/alloc/stack_free.rs:LL:CC diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.tree.stderr b/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.tree.stderr index 70a96197b00a..a033065613fc 100644 --- a/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.tree.stderr +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.tree.stderr @@ -20,11 +20,11 @@ help: the protected tag was created here, in the initial state Frozen LL | fn safe(x: &i32, y: &mut Cell) { | ^ = note: stack backtrace: - 0: std::mem::replace + 0: std::mem::replace:: at RUSTLIB/core/src/mem/mod.rs:LL:CC - 1: std::cell::Cell::replace + 1: std::cell::Cell::::replace at RUSTLIB/core/src/cell.rs:LL:CC - 2: std::cell::Cell::set + 2: std::cell::Cell::::set at RUSTLIB/core/src/cell.rs:LL:CC 3: safe at tests/fail/both_borrows/aliasing_mut4.rs:LL:CC diff --git a/src/tools/miri/tests/fail/both_borrows/buggy_split_at_mut.stack.stderr b/src/tools/miri/tests/fail/both_borrows/buggy_split_at_mut.stack.stderr index 7a34e7d4e991..b3de50d94f55 100644 --- a/src/tools/miri/tests/fail/both_borrows/buggy_split_at_mut.stack.stderr +++ b/src/tools/miri/tests/fail/both_borrows/buggy_split_at_mut.stack.stderr @@ -23,7 +23,7 @@ help: was later invalidated at offsets [0x0..0x10] by a Unique retag LL | from_raw_parts_mut(ptr.offset(mid as isize), len - mid), | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ = note: stack backtrace: - 0: safe::split_at_mut + 0: safe::split_at_mut:: at tests/fail/both_borrows/buggy_split_at_mut.rs:LL:CC 1: main at tests/fail/both_borrows/buggy_split_at_mut.rs:LL:CC diff --git a/src/tools/miri/tests/fail/both_borrows/drop_in_place_protector.stack.stderr b/src/tools/miri/tests/fail/both_borrows/drop_in_place_protector.stack.stderr index 17b704ce8038..586dcdf8515d 100644 --- a/src/tools/miri/tests/fail/both_borrows/drop_in_place_protector.stack.stderr +++ b/src/tools/miri/tests/fail/both_borrows/drop_in_place_protector.stack.stderr @@ -19,11 +19,11 @@ LL | core::ptr::drop_in_place(x); = note: stack backtrace: 0: ::drop at tests/fail/both_borrows/drop_in_place_protector.rs:LL:CC - 1: std::ptr::drop_glue - shim(Some(HasDrop)) + 1: std::ptr::drop_glue:: - shim(Some(HasDrop)) at RUSTLIB/core/src/ptr/mod.rs:LL:CC - 2: std::ptr::drop_glue - shim(Some((HasDrop, u8))) + 2: std::ptr::drop_glue::<(HasDrop, u8)> - shim(Some((HasDrop, u8))) at RUSTLIB/core/src/ptr/mod.rs:LL:CC - 3: std::ptr::drop_in_place + 3: std::ptr::drop_in_place::<(HasDrop, u8)> at RUSTLIB/core/src/ptr/mod.rs:LL:CC 4: main at tests/fail/both_borrows/drop_in_place_protector.rs:LL:CC diff --git a/src/tools/miri/tests/fail/both_borrows/drop_in_place_protector.tree.stderr b/src/tools/miri/tests/fail/both_borrows/drop_in_place_protector.tree.stderr index 61413c8551a8..867711bc6741 100644 --- a/src/tools/miri/tests/fail/both_borrows/drop_in_place_protector.tree.stderr +++ b/src/tools/miri/tests/fail/both_borrows/drop_in_place_protector.tree.stderr @@ -22,11 +22,11 @@ LL | core::ptr::drop_in_place(x); = note: stack backtrace: 0: ::drop at tests/fail/both_borrows/drop_in_place_protector.rs:LL:CC - 1: std::ptr::drop_glue - shim(Some(HasDrop)) + 1: std::ptr::drop_glue:: - shim(Some(HasDrop)) at RUSTLIB/core/src/ptr/mod.rs:LL:CC - 2: std::ptr::drop_glue - shim(Some((HasDrop, u8))) + 2: std::ptr::drop_glue::<(HasDrop, u8)> - shim(Some((HasDrop, u8))) at RUSTLIB/core/src/ptr/mod.rs:LL:CC - 3: std::ptr::drop_in_place + 3: std::ptr::drop_in_place::<(HasDrop, u8)> at RUSTLIB/core/src/ptr/mod.rs:LL:CC 4: main at tests/fail/both_borrows/drop_in_place_protector.rs:LL:CC diff --git a/src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.stack.stderr b/src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.stack.stderr index d7688680e7da..2f10e7df418f 100644 --- a/src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.stack.stderr +++ b/src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.stack.stderr @@ -17,13 +17,13 @@ help: is this argument LL | fn dealloc_while_running(_n: Newtype<'_>, dealloc: impl FnOnce()) { | ^^ = note: stack backtrace: - 0: std::boxed::Box::from_raw_in + 0: std::boxed::Box::::from_raw_in at RUSTLIB/alloc/src/boxed.rs:LL:CC - 1: std::boxed::Box::from_raw + 1: std::boxed::Box::::from_raw at RUSTLIB/alloc/src/boxed.rs:LL:CC 2: main::{closure#0} at tests/fail/both_borrows/newtype_pair_retagging.rs:LL:CC - 3: dealloc_while_running + 3: dealloc_while_running::<{closure@tests/fail/both_borrows/newtype_pair_retagging.rs:LL:CC}> at tests/fail/both_borrows/newtype_pair_retagging.rs:LL:CC 4: main at tests/fail/both_borrows/newtype_pair_retagging.rs:LL:CC diff --git a/src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.tree.stderr b/src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.tree.stderr index e84fa4281d13..58c43ebe4aee 100644 --- a/src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.tree.stderr +++ b/src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.tree.stderr @@ -28,13 +28,13 @@ LL | || drop(Box::from_raw(ptr)), = note: stack backtrace: 0: as std::ops::Drop>::drop at RUSTLIB/alloc/src/boxed.rs:LL:CC - 1: std::ptr::drop_glue)) + 1: std::ptr::drop_glue::> - shim(Some(std::boxed::Box)) at RUSTLIB/core/src/ptr/mod.rs:LL:CC - 2: std::mem::drop + 2: std::mem::drop::> at RUSTLIB/core/src/mem/mod.rs:LL:CC 3: main::{closure#0} at tests/fail/both_borrows/newtype_pair_retagging.rs:LL:CC - 4: dealloc_while_running + 4: dealloc_while_running::<{closure@tests/fail/both_borrows/newtype_pair_retagging.rs:LL:CC}> at tests/fail/both_borrows/newtype_pair_retagging.rs:LL:CC 5: main at tests/fail/both_borrows/newtype_pair_retagging.rs:LL:CC diff --git a/src/tools/miri/tests/fail/both_borrows/newtype_retagging.stack.stderr b/src/tools/miri/tests/fail/both_borrows/newtype_retagging.stack.stderr index b7b0f2812669..a173cf25e9e7 100644 --- a/src/tools/miri/tests/fail/both_borrows/newtype_retagging.stack.stderr +++ b/src/tools/miri/tests/fail/both_borrows/newtype_retagging.stack.stderr @@ -17,13 +17,13 @@ help: is this argument LL | fn dealloc_while_running(_n: Newtype<'_>, dealloc: impl FnOnce()) { | ^^ = note: stack backtrace: - 0: std::boxed::Box::from_raw_in + 0: std::boxed::Box::::from_raw_in at RUSTLIB/alloc/src/boxed.rs:LL:CC - 1: std::boxed::Box::from_raw + 1: std::boxed::Box::::from_raw at RUSTLIB/alloc/src/boxed.rs:LL:CC 2: main::{closure#0} at tests/fail/both_borrows/newtype_retagging.rs:LL:CC - 3: dealloc_while_running + 3: dealloc_while_running::<{closure@tests/fail/both_borrows/newtype_retagging.rs:LL:CC}> at tests/fail/both_borrows/newtype_retagging.rs:LL:CC 4: main at tests/fail/both_borrows/newtype_retagging.rs:LL:CC diff --git a/src/tools/miri/tests/fail/both_borrows/newtype_retagging.tree.stderr b/src/tools/miri/tests/fail/both_borrows/newtype_retagging.tree.stderr index 1151a44410cc..a5b66643144e 100644 --- a/src/tools/miri/tests/fail/both_borrows/newtype_retagging.tree.stderr +++ b/src/tools/miri/tests/fail/both_borrows/newtype_retagging.tree.stderr @@ -28,13 +28,13 @@ LL | || drop(Box::from_raw(ptr)), = note: stack backtrace: 0: as std::ops::Drop>::drop at RUSTLIB/alloc/src/boxed.rs:LL:CC - 1: std::ptr::drop_glue)) + 1: std::ptr::drop_glue::> - shim(Some(std::boxed::Box)) at RUSTLIB/core/src/ptr/mod.rs:LL:CC - 2: std::mem::drop + 2: std::mem::drop::> at RUSTLIB/core/src/mem/mod.rs:LL:CC 3: main::{closure#0} at tests/fail/both_borrows/newtype_retagging.rs:LL:CC - 4: dealloc_while_running + 4: dealloc_while_running::<{closure@tests/fail/both_borrows/newtype_retagging.rs:LL:CC}> at tests/fail/both_borrows/newtype_retagging.rs:LL:CC 5: main at tests/fail/both_borrows/newtype_retagging.rs:LL:CC diff --git a/src/tools/miri/tests/fail/coroutine-pinned-moved.stderr b/src/tools/miri/tests/fail/coroutine-pinned-moved.stderr index 0c57c6894a08..7998db2424b3 100644 --- a/src/tools/miri/tests/fail/coroutine-pinned-moved.stderr +++ b/src/tools/miri/tests/fail/coroutine-pinned-moved.stderr @@ -21,7 +21,7 @@ LL | }; // *deallocate* coroutine_iterator at tests/fail/coroutine-pinned-moved.rs:LL:CC 1: as std::iter::Iterator>::next at tests/fail/coroutine-pinned-moved.rs:LL:CC - 2: std::boxed::iter::next + 2: std::boxed::iter::>>::next at RUSTLIB/alloc/src/boxed/iter.rs:LL:CC 3: main at tests/fail/coroutine-pinned-moved.rs:LL:CC diff --git a/src/tools/miri/tests/fail/memleak_rc.stderr b/src/tools/miri/tests/fail/memleak_rc.stderr index 91097a99117f..2c22367f5246 100644 --- a/src/tools/miri/tests/fail/memleak_rc.stderr +++ b/src/tools/miri/tests/fail/memleak_rc.stderr @@ -5,7 +5,7 @@ LL | Box::leak(Box::new(RcInner { strong: Cell::new(1), weak: Ce | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: stack backtrace: - 0: std::rc::Rc::new + 0: std::rc::Rc::>>::new at RUSTLIB/alloc/src/rc.rs:LL:CC 1: main at tests/fail/memleak_rc.rs:LL:CC diff --git a/src/tools/miri/tests/fail/panic/bad_unwind.stderr b/src/tools/miri/tests/fail/panic/bad_unwind.stderr index d47f7b5b10c1..cde80858d0fd 100644 --- a/src/tools/miri/tests/fail/panic/bad_unwind.stderr +++ b/src/tools/miri/tests/fail/panic/bad_unwind.stderr @@ -14,11 +14,11 @@ LL | std::panic::catch_unwind(|| unwind()).unwrap_err(); = note: stack backtrace: 0: main::{closure#0} at tests/fail/panic/bad_unwind.rs:LL:CC - 1: std::panicking::catch_unwind::do_call + 1: std::panicking::catch_unwind::do_call::<{closure@tests/fail/panic/bad_unwind.rs:LL:CC}, ()> at RUSTLIB/std/src/panicking.rs:LL:CC - 2: std::panicking::catch_unwind + 2: std::panicking::catch_unwind::<(), {closure@tests/fail/panic/bad_unwind.rs:LL:CC}> at RUSTLIB/std/src/panicking.rs:LL:CC - 3: std::panic::catch_unwind + 3: std::panic::catch_unwind::<{closure@tests/fail/panic/bad_unwind.rs:LL:CC}, ()> at RUSTLIB/std/src/panic.rs:LL:CC 4: main at tests/fail/panic/bad_unwind.rs:LL:CC diff --git a/src/tools/miri/tests/fail/panic/panic_abort1.stderr b/src/tools/miri/tests/fail/panic/panic_abort1.stderr index 4135ceadc539..079eb4117b06 100644 --- a/src/tools/miri/tests/fail/panic/panic_abort1.stderr +++ b/src/tools/miri/tests/fail/panic/panic_abort1.stderr @@ -20,7 +20,7 @@ LL | crate::process::abort(); at RUSTLIB/std/src/panicking.rs:LL:CC 4: std::panicking::panic_handler::{closure#0} at RUSTLIB/std/src/panicking.rs:LL:CC - 5: std::sys::backtrace::__rust_end_short_backtrace + 5: std::sys::backtrace::__rust_end_short_backtrace::<{closure@std::panicking::panic_handler::{closure#0}}, !> at RUSTLIB/std/src/sys/backtrace.rs:LL:CC 6: std::panicking::panic_handler at RUSTLIB/std/src/panicking.rs:LL:CC diff --git a/src/tools/miri/tests/fail/panic/panic_abort2.stderr b/src/tools/miri/tests/fail/panic/panic_abort2.stderr index 5b485604037c..5437fa077a0a 100644 --- a/src/tools/miri/tests/fail/panic/panic_abort2.stderr +++ b/src/tools/miri/tests/fail/panic/panic_abort2.stderr @@ -20,7 +20,7 @@ LL | crate::process::abort(); at RUSTLIB/std/src/panicking.rs:LL:CC 4: std::panicking::panic_handler::{closure#0} at RUSTLIB/std/src/panicking.rs:LL:CC - 5: std::sys::backtrace::__rust_end_short_backtrace + 5: std::sys::backtrace::__rust_end_short_backtrace::<{closure@std::panicking::panic_handler::{closure#0}}, !> at RUSTLIB/std/src/sys/backtrace.rs:LL:CC 6: std::panicking::panic_handler at RUSTLIB/std/src/panicking.rs:LL:CC diff --git a/src/tools/miri/tests/fail/panic/panic_abort3.stderr b/src/tools/miri/tests/fail/panic/panic_abort3.stderr index e4179f93f931..022515ebecfd 100644 --- a/src/tools/miri/tests/fail/panic/panic_abort3.stderr +++ b/src/tools/miri/tests/fail/panic/panic_abort3.stderr @@ -20,7 +20,7 @@ LL | crate::process::abort(); at RUSTLIB/std/src/panicking.rs:LL:CC 4: std::panicking::panic_handler::{closure#0} at RUSTLIB/std/src/panicking.rs:LL:CC - 5: std::sys::backtrace::__rust_end_short_backtrace + 5: std::sys::backtrace::__rust_end_short_backtrace::<{closure@std::panicking::panic_handler::{closure#0}}, !> at RUSTLIB/std/src/sys/backtrace.rs:LL:CC 6: std::panicking::panic_handler at RUSTLIB/std/src/panicking.rs:LL:CC diff --git a/src/tools/miri/tests/fail/panic/panic_abort4.stderr b/src/tools/miri/tests/fail/panic/panic_abort4.stderr index d2e50b87068f..a39037d52855 100644 --- a/src/tools/miri/tests/fail/panic/panic_abort4.stderr +++ b/src/tools/miri/tests/fail/panic/panic_abort4.stderr @@ -20,7 +20,7 @@ LL | crate::process::abort(); at RUSTLIB/std/src/panicking.rs:LL:CC 4: std::panicking::panic_handler::{closure#0} at RUSTLIB/std/src/panicking.rs:LL:CC - 5: std::sys::backtrace::__rust_end_short_backtrace + 5: std::sys::backtrace::__rust_end_short_backtrace::<{closure@std::panicking::panic_handler::{closure#0}}, !> at RUSTLIB/std/src/sys/backtrace.rs:LL:CC 6: std::panicking::panic_handler at RUSTLIB/std/src/panicking.rs:LL:CC diff --git a/src/tools/miri/tests/fail/shims/fs/isolated_file.stderr b/src/tools/miri/tests/fail/shims/fs/isolated_file.stderr index 09501f98a9aa..5012e4c7ca8d 100644 --- a/src/tools/miri/tests/fail/shims/fs/isolated_file.stderr +++ b/src/tools/miri/tests/fail/shims/fs/isolated_file.stderr @@ -9,25 +9,25 @@ LL | let fd = cvt_r(|| unsafe { open64(path.as_ptr(), flags, opts.mode a = note: stack backtrace: 0: std::sys::fs::PLATFORM::File::open_c::{closure#0} at RUSTLIB/std/src/sys/fs/PLATFORM.rs:LL:CC - 1: std::sys::pal::PLATFORM::cvt_r + 1: std::sys::pal::PLATFORM::cvt_r:: at RUSTLIB/std/src/sys/pal/PLATFORM/mod.rs:LL:CC 2: std::sys::fs::PLATFORM::File::open_c at RUSTLIB/std/src/sys/fs/PLATFORM.rs:LL:CC 3: std::sys::fs::PLATFORM::File::open::{closure#0} at RUSTLIB/std/src/sys/fs/PLATFORM.rs:LL:CC - 4: std::sys::helpers::small_c_string::run_with_cstr_stack + 4: std::sys::helpers::small_c_string::run_with_cstr_stack:: at RUSTLIB/std/src/sys/helpers/small_c_string.rs:LL:CC - 5: std::sys::helpers::small_c_string::run_with_cstr + 5: std::sys::helpers::small_c_string::run_with_cstr:: at RUSTLIB/std/src/sys/helpers/small_c_string.rs:LL:CC - 6: std::sys::helpers::small_c_string::run_path_with_cstr + 6: std::sys::helpers::small_c_string::run_path_with_cstr:: at RUSTLIB/std/src/sys/helpers/small_c_string.rs:LL:CC 7: std::sys::fs::PLATFORM::File::open at RUSTLIB/std/src/sys/fs/PLATFORM.rs:LL:CC 8: std::fs::OpenOptions::_open at RUSTLIB/std/src/fs.rs:LL:CC - 9: std::fs::OpenOptions::open + 9: std::fs::OpenOptions::open::<&std::path::Path> at RUSTLIB/std/src/fs.rs:LL:CC - 10: std::fs::File::open + 10: std::fs::File::open::<&str> at RUSTLIB/std/src/fs.rs:LL:CC 11: main at tests/fail/shims/fs/isolated_file.rs:LL:CC diff --git a/src/tools/miri/tests/fail/stacked_borrows/deallocate_against_protector1.stderr b/src/tools/miri/tests/fail/stacked_borrows/deallocate_against_protector1.stderr index 1d9bfe544068..9f0df14ee4dd 100644 --- a/src/tools/miri/tests/fail/stacked_borrows/deallocate_against_protector1.stderr +++ b/src/tools/miri/tests/fail/stacked_borrows/deallocate_against_protector1.stderr @@ -9,9 +9,9 @@ LL | self.1.deallocate(From::from(ptr.cast()), layout); = note: stack backtrace: 0: as std::ops::Drop>::drop at RUSTLIB/alloc/src/boxed.rs:LL:CC - 1: std::ptr::drop_glue)) + 1: std::ptr::drop_glue::> - shim(Some(std::boxed::Box)) at RUSTLIB/core/src/ptr/mod.rs:LL:CC - 2: std::mem::drop + 2: std::mem::drop::> at RUSTLIB/core/src/mem/mod.rs:LL:CC 3: main::{closure#0} at tests/fail/stacked_borrows/deallocate_against_protector1.rs:LL:CC diff --git a/src/tools/miri/tests/fail/stacked_borrows/drop_in_place_retag.stderr b/src/tools/miri/tests/fail/stacked_borrows/drop_in_place_retag.stderr index 96e31615c20e..c9a3c93916fd 100644 --- a/src/tools/miri/tests/fail/stacked_borrows/drop_in_place_retag.stderr +++ b/src/tools/miri/tests/fail/stacked_borrows/drop_in_place_retag.stderr @@ -12,7 +12,7 @@ help: was created by a SharedReadOnly retag at offsets [0x0..0x1] LL | let x = core::ptr::addr_of!(x); | ^^^^^^^^^^^^^^^^^^^^^^ = note: stack backtrace: - 0: std::ptr::drop_in_place + 0: std::ptr::drop_in_place:: at RUSTLIB/core/src/ptr/mod.rs:LL:CC 1: main at tests/fail/stacked_borrows/drop_in_place_retag.rs:LL:CC diff --git a/src/tools/miri/tests/fail/tail_calls/cc-mismatch.stderr b/src/tools/miri/tests/fail/tail_calls/cc-mismatch.stderr index c7e471f570c8..a9440caa3e3b 100644 --- a/src/tools/miri/tests/fail/tail_calls/cc-mismatch.stderr +++ b/src/tools/miri/tests/fail/tail_calls/cc-mismatch.stderr @@ -9,29 +9,29 @@ LL | extern "rust-call" fn call_once(self, args: Args) -> Self::Output; = note: stack backtrace: 0: >::call_once - shim(fn()) at RUSTLIB/core/src/ops/function.rs:LL:CC - 1: std::sys::backtrace::__rust_begin_short_backtrace + 1: std::sys::backtrace::__rust_begin_short_backtrace:: at RUSTLIB/std/src/sys/backtrace.rs:LL:CC - 2: std::rt::lang_start::{closure#0} + 2: std::rt::lang_start::<()>::{closure#0} at RUSTLIB/std/src/rt.rs:LL:CC - 3: std::ops::function::impls::call_once + 3: std::ops::function::impls:: for &dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe>::call_once at RUSTLIB/core/src/ops/function.rs:LL:CC - 4: std::panicking::catch_unwind::do_call + 4: std::panicking::catch_unwind::do_call::<&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe, i32> at RUSTLIB/std/src/panicking.rs:LL:CC - 5: std::panicking::catch_unwind + 5: std::panicking::catch_unwind:: i32 + std::marker::Sync + std::panic::RefUnwindSafe> at RUSTLIB/std/src/panicking.rs:LL:CC - 6: std::panic::catch_unwind + 6: std::panic::catch_unwind::<&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe, i32> at RUSTLIB/std/src/panic.rs:LL:CC 7: std::rt::lang_start_internal::{closure#0} at RUSTLIB/std/src/rt.rs:LL:CC - 8: std::panicking::catch_unwind::do_call + 8: std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize> at RUSTLIB/std/src/panicking.rs:LL:CC - 9: std::panicking::catch_unwind + 9: std::panicking::catch_unwind:: at RUSTLIB/std/src/panicking.rs:LL:CC - 10: std::panic::catch_unwind + 10: std::panic::catch_unwind::<{closure@std::rt::lang_start_internal::{closure#0}}, isize> at RUSTLIB/std/src/panic.rs:LL:CC 11: std::rt::lang_start_internal at RUSTLIB/std/src/rt.rs:LL:CC - 12: std::rt::lang_start + 12: std::rt::lang_start::<()> at RUSTLIB/std/src/rt.rs:LL:CC error: aborting due to 1 previous error diff --git a/src/tools/miri/tests/fail/tls_macro_leak.stderr b/src/tools/miri/tests/fail/tls_macro_leak.stderr index 3f8240f44547..135dde879b9e 100644 --- a/src/tools/miri/tests/fail/tls_macro_leak.stderr +++ b/src/tools/miri/tests/fail/tls_macro_leak.stderr @@ -7,9 +7,9 @@ LL | cell.set(Some(Box::leak(Box::new(123)))); = note: stack backtrace: 0: main::{closure#0}::{closure#0} at tests/fail/tls_macro_leak.rs:LL:CC - 1: std::thread::LocalKey::>>::try_with + 1: std::thread::LocalKey::>>::try_with::<{closure@tests/fail/tls_macro_leak.rs:LL:CC}, ()> at RUSTLIB/std/src/thread/local.rs:LL:CC - 2: std::thread::LocalKey::>>::with + 2: std::thread::LocalKey::>>::with::<{closure@tests/fail/tls_macro_leak.rs:LL:CC}, ()> at RUSTLIB/std/src/thread/local.rs:LL:CC 3: main::{closure#0} at tests/fail/tls_macro_leak.rs:LL:CC diff --git a/src/tools/miri/tests/fail/tree_borrows/implicit_writes/ptr_write.stderr b/src/tools/miri/tests/fail/tree_borrows/implicit_writes/ptr_write.stderr index e88e13104815..baec37b17f02 100644 --- a/src/tools/miri/tests/fail/tree_borrows/implicit_writes/ptr_write.stderr +++ b/src/tools/miri/tests/fail/tree_borrows/implicit_writes/ptr_write.stderr @@ -20,7 +20,7 @@ help: the protected tag was created here, in the initial state Reserved LL | fn dereference(x: T, y: *mut u8) -> T { | ^ = note: stack backtrace: - 0: dereference + 0: dereference::<&mut u8> at tests/fail/tree_borrows/implicit_writes/ptr_write.rs:LL:CC 1: main at tests/fail/tree_borrows/implicit_writes/ptr_write.rs:LL:CC diff --git a/src/tools/miri/tests/fail/tree_borrows/implicit_writes/ptr_write_box.stderr b/src/tools/miri/tests/fail/tree_borrows/implicit_writes/ptr_write_box.stderr index 9951fd56ada0..9e0bc972dce4 100644 --- a/src/tools/miri/tests/fail/tree_borrows/implicit_writes/ptr_write_box.stderr +++ b/src/tools/miri/tests/fail/tree_borrows/implicit_writes/ptr_write_box.stderr @@ -20,7 +20,7 @@ help: the protected tag was created here, in the initial state Reserved LL | fn dereference(x: T, y: *mut u8) -> T { | ^ = note: stack backtrace: - 0: dereference + 0: dereference::> at tests/fail/tree_borrows/implicit_writes/ptr_write_box.rs:LL:CC 1: main at tests/fail/tree_borrows/implicit_writes/ptr_write_box.rs:LL:CC diff --git a/src/tools/miri/tests/fail/tree_borrows/implicit_writes/ptr_write_unsafe_cell.stderr b/src/tools/miri/tests/fail/tree_borrows/implicit_writes/ptr_write_unsafe_cell.stderr index 87cf0c8ba2de..bb65e846ebe3 100644 --- a/src/tools/miri/tests/fail/tree_borrows/implicit_writes/ptr_write_unsafe_cell.stderr +++ b/src/tools/miri/tests/fail/tree_borrows/implicit_writes/ptr_write_unsafe_cell.stderr @@ -20,7 +20,7 @@ help: the protected tag was created here, in the initial state Reserved LL | fn dereference(x: T, y: *mut u8) -> T { | ^ = note: stack backtrace: - 0: dereference + 0: dereference::<&mut std::cell::UnsafeCell> at tests/fail/tree_borrows/implicit_writes/ptr_write_unsafe_cell.rs:LL:CC 1: main at tests/fail/tree_borrows/implicit_writes/ptr_write_unsafe_cell.rs:LL:CC diff --git a/src/tools/miri/tests/fail/tree_borrows/strongly-protected.stderr b/src/tools/miri/tests/fail/tree_borrows/strongly-protected.stderr index d2a11659460e..9af0414ce606 100644 --- a/src/tools/miri/tests/fail/tree_borrows/strongly-protected.stderr +++ b/src/tools/miri/tests/fail/tree_borrows/strongly-protected.stderr @@ -21,9 +21,9 @@ LL | fn inner(x: &mut i32, f: fn(*mut i32)) { = note: stack backtrace: 0: as std::ops::Drop>::drop at RUSTLIB/alloc/src/boxed.rs:LL:CC - 1: std::ptr::drop_glue)) + 1: std::ptr::drop_glue::> - shim(Some(std::boxed::Box)) at RUSTLIB/core/src/ptr/mod.rs:LL:CC - 2: std::mem::drop + 2: std::mem::drop::> at RUSTLIB/core/src/mem/mod.rs:LL:CC 3: main::{closure#0} at tests/fail/tree_borrows/strongly-protected.rs:LL:CC diff --git a/src/tools/miri/tests/fail/tree_borrows/wildcard/strongly_protected_wildcard.stderr b/src/tools/miri/tests/fail/tree_borrows/wildcard/strongly_protected_wildcard.stderr index c192c2d00d08..e6c68dd3de92 100644 --- a/src/tools/miri/tests/fail/tree_borrows/wildcard/strongly_protected_wildcard.stderr +++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/strongly_protected_wildcard.stderr @@ -21,9 +21,9 @@ LL | fn inner(x: &mut i32, f: fn(usize)) { = note: stack backtrace: 0: as std::ops::Drop>::drop at RUSTLIB/alloc/src/boxed.rs:LL:CC - 1: std::ptr::drop_glue)) + 1: std::ptr::drop_glue::> - shim(Some(std::boxed::Box)) at RUSTLIB/core/src/ptr/mod.rs:LL:CC - 2: std::mem::drop + 2: std::mem::drop::> at RUSTLIB/core/src/mem/mod.rs:LL:CC 3: main::{closure#0} at tests/fail/tree_borrows/wildcard/strongly_protected_wildcard.rs:LL:CC diff --git a/src/tools/miri/tests/fail/unaligned_pointers/drop_in_place.stderr b/src/tools/miri/tests/fail/unaligned_pointers/drop_in_place.stderr index 603d582cde80..52096d5c7ecd 100644 --- a/src/tools/miri/tests/fail/unaligned_pointers/drop_in_place.stderr +++ b/src/tools/miri/tests/fail/unaligned_pointers/drop_in_place.stderr @@ -7,7 +7,7 @@ LL | unsafe { drop_glue(&mut *to_drop) } = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information = note: stack backtrace: - 0: std::ptr::drop_in_place + 0: std::ptr::drop_in_place:: at RUSTLIB/core/src/ptr/mod.rs:LL:CC 1: main at tests/fail/unaligned_pointers/drop_in_place.rs:LL:CC diff --git a/src/tools/miri/tests/fail/unaligned_pointers/reference_to_packed.stderr b/src/tools/miri/tests/fail/unaligned_pointers/reference_to_packed.stderr index d8252e51ae84..28ed982a0864 100644 --- a/src/tools/miri/tests/fail/unaligned_pointers/reference_to_packed.stderr +++ b/src/tools/miri/tests/fail/unaligned_pointers/reference_to_packed.stderr @@ -7,7 +7,7 @@ LL | mem::transmute(x) = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information = note: stack backtrace: - 0: raw_to_ref + 0: raw_to_ref::<'_, i32> at tests/fail/unaligned_pointers/reference_to_packed.rs:LL:CC 1: main at tests/fail/unaligned_pointers/reference_to_packed.rs:LL:CC diff --git a/src/tools/miri/tests/fail/uninit/uninit_alloc_diagnostic.stderr b/src/tools/miri/tests/fail/uninit/uninit_alloc_diagnostic.stderr index e0caff6a21a9..26d5ffa62812 100644 --- a/src/tools/miri/tests/fail/uninit/uninit_alloc_diagnostic.stderr +++ b/src/tools/miri/tests/fail/uninit/uninit_alloc_diagnostic.stderr @@ -9,7 +9,7 @@ LL | let mut order = unsafe { compare_bytes(left, right, len) as isize } = note: stack backtrace: 0: ::compare at RUSTLIB/core/src/slice/cmp.rs:LL:CC - 1: core::slice::cmp::cmp + 1: core::slice::cmp::::cmp at RUSTLIB/core/src/slice/cmp.rs:LL:CC 2: main at tests/fail/uninit/uninit_alloc_diagnostic.rs:LL:CC diff --git a/src/tools/miri/tests/fail/uninit/uninit_alloc_diagnostic_with_provenance.stderr b/src/tools/miri/tests/fail/uninit/uninit_alloc_diagnostic_with_provenance.stderr index 28609ddec6a1..4e532293410e 100644 --- a/src/tools/miri/tests/fail/uninit/uninit_alloc_diagnostic_with_provenance.stderr +++ b/src/tools/miri/tests/fail/uninit/uninit_alloc_diagnostic_with_provenance.stderr @@ -9,7 +9,7 @@ LL | let mut order = unsafe { compare_bytes(left, right, len) as isize } = note: stack backtrace: 0: ::compare at RUSTLIB/core/src/slice/cmp.rs:LL:CC - 1: core::slice::cmp::cmp + 1: core::slice::cmp::::cmp at RUSTLIB/core/src/slice/cmp.rs:LL:CC 2: main at tests/fail/uninit/uninit_alloc_diagnostic_with_provenance.rs:LL:CC diff --git a/src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_double_free.stderr b/src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_double_free.stderr index 2ba64ac032a8..9eb9183611bc 100644 --- a/src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_double_free.stderr +++ b/src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_double_free.stderr @@ -25,7 +25,7 @@ LL | dealloc(ptr as *mut u8, Layout::new::()) at tests/genmc/fail/atomics/atomic_ptr_double_free.rs:LL:CC 2: as std::ops::FnOnce<()>>::call_once at RUSTLIB/alloc/src/boxed.rs:LL:CC - 3: genmc::spawn_pthread_closure::thread_func + 3: genmc::spawn_pthread_closure::thread_func::<{closure@tests/genmc/fail/atomics/atomic_ptr_double_free.rs:LL:CC}> at tests/genmc/fail/atomics/../../../utils/genmc.rs:LL:CC note: the last function in that backtrace got called indirectly due to this code --> tests/genmc/fail/atomics/../../../utils/genmc.rs:LL:CC diff --git a/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_alloc_race.dealloc.stderr b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_alloc_race.dealloc.stderr index aa51e1213d0c..c7f8cba09f6e 100644 --- a/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_alloc_race.dealloc.stderr +++ b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_alloc_race.dealloc.stderr @@ -13,7 +13,7 @@ LL | dealloc(b as *mut u8, Layout::new::()); at tests/genmc/fail/data_race/atomic_ptr_alloc_race.rs:LL:CC 1: as std::ops::FnOnce<()>>::call_once at RUSTLIB/alloc/src/boxed.rs:LL:CC - 2: genmc::spawn_pthread_closure::thread_func + 2: genmc::spawn_pthread_closure::thread_func::<{closure@tests/genmc/fail/data_race/atomic_ptr_alloc_race.rs:LL:CC}> at tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC note: the last function in that backtrace got called indirectly due to this code --> tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC diff --git a/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_alloc_race.write.stderr b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_alloc_race.write.stderr index 77e9817a55c7..da50ff861234 100644 --- a/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_alloc_race.write.stderr +++ b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_alloc_race.write.stderr @@ -13,7 +13,7 @@ LL | *b = 42; at tests/genmc/fail/data_race/atomic_ptr_alloc_race.rs:LL:CC 1: as std::ops::FnOnce<()>>::call_once at RUSTLIB/alloc/src/boxed.rs:LL:CC - 2: genmc::spawn_pthread_closure::thread_func + 2: genmc::spawn_pthread_closure::thread_func::<{closure@tests/genmc/fail/data_race/atomic_ptr_alloc_race.rs:LL:CC}> at tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC note: the last function in that backtrace got called indirectly due to this code --> tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC diff --git a/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_dealloc_write_race.stderr b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_dealloc_write_race.stderr index 47df94404efa..de3b554d4ac1 100644 --- a/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_dealloc_write_race.stderr +++ b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_dealloc_write_race.stderr @@ -23,7 +23,7 @@ LL | }), at tests/genmc/fail/data_race/atomic_ptr_dealloc_write_race.rs:LL:CC 1: as std::ops::FnOnce<()>>::call_once at RUSTLIB/alloc/src/boxed.rs:LL:CC - 2: genmc::spawn_pthread_closure::thread_func + 2: genmc::spawn_pthread_closure::thread_func::<{closure@tests/genmc/fail/data_race/atomic_ptr_dealloc_write_race.rs:LL:CC}> at tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC note: the last function in that backtrace got called indirectly due to this code --> tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC diff --git a/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_write_dealloc_race.stderr b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_write_dealloc_race.stderr index e2c87b2d25e1..248009cb1ed0 100644 --- a/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_write_dealloc_race.stderr +++ b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_write_dealloc_race.stderr @@ -13,7 +13,7 @@ LL | }), at tests/genmc/fail/data_race/atomic_ptr_write_dealloc_race.rs:LL:CC 1: as std::ops::FnOnce<()>>::call_once at RUSTLIB/alloc/src/boxed.rs:LL:CC - 2: genmc::spawn_pthread_closure::thread_func + 2: genmc::spawn_pthread_closure::thread_func::<{closure@tests/genmc/fail/data_race/atomic_ptr_write_dealloc_race.rs:LL:CC}> at tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC note: the last function in that backtrace got called indirectly due to this code --> tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC diff --git a/src/tools/miri/tests/genmc/fail/data_race/mpu2_rels_rlx.stderr b/src/tools/miri/tests/genmc/fail/data_race/mpu2_rels_rlx.stderr index a3a15a71ce15..1ccbf7683f20 100644 --- a/src/tools/miri/tests/genmc/fail/data_race/mpu2_rels_rlx.stderr +++ b/src/tools/miri/tests/genmc/fail/data_race/mpu2_rels_rlx.stderr @@ -13,7 +13,7 @@ LL | X = 2; at tests/genmc/fail/data_race/mpu2_rels_rlx.rs:LL:CC 1: as std::ops::FnOnce<()>>::call_once at RUSTLIB/alloc/src/boxed.rs:LL:CC - 2: genmc::spawn_pthread_closure::thread_func + 2: genmc::spawn_pthread_closure::thread_func::<{closure@tests/genmc/fail/data_race/mpu2_rels_rlx.rs:LL:CC}> at tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC note: the last function in that backtrace got called indirectly due to this code --> tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC diff --git a/src/tools/miri/tests/genmc/fail/data_race/weak_orderings.rel_rlx.stderr b/src/tools/miri/tests/genmc/fail/data_race/weak_orderings.rel_rlx.stderr index 1220c0c09cbe..e589153e1e81 100644 --- a/src/tools/miri/tests/genmc/fail/data_race/weak_orderings.rel_rlx.stderr +++ b/src/tools/miri/tests/genmc/fail/data_race/weak_orderings.rel_rlx.stderr @@ -13,7 +13,7 @@ LL | X = 2; at tests/genmc/fail/data_race/weak_orderings.rs:LL:CC 1: as std::ops::FnOnce<()>>::call_once at RUSTLIB/alloc/src/boxed.rs:LL:CC - 2: genmc::spawn_pthread_closure::thread_func + 2: genmc::spawn_pthread_closure::thread_func::<{closure@tests/genmc/fail/data_race/weak_orderings.rs:LL:CC}> at tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC note: the last function in that backtrace got called indirectly due to this code --> tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC diff --git a/src/tools/miri/tests/genmc/fail/data_race/weak_orderings.rlx_acq.stderr b/src/tools/miri/tests/genmc/fail/data_race/weak_orderings.rlx_acq.stderr index 1220c0c09cbe..e589153e1e81 100644 --- a/src/tools/miri/tests/genmc/fail/data_race/weak_orderings.rlx_acq.stderr +++ b/src/tools/miri/tests/genmc/fail/data_race/weak_orderings.rlx_acq.stderr @@ -13,7 +13,7 @@ LL | X = 2; at tests/genmc/fail/data_race/weak_orderings.rs:LL:CC 1: as std::ops::FnOnce<()>>::call_once at RUSTLIB/alloc/src/boxed.rs:LL:CC - 2: genmc::spawn_pthread_closure::thread_func + 2: genmc::spawn_pthread_closure::thread_func::<{closure@tests/genmc/fail/data_race/weak_orderings.rs:LL:CC}> at tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC note: the last function in that backtrace got called indirectly due to this code --> tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC diff --git a/src/tools/miri/tests/genmc/fail/data_race/weak_orderings.rlx_rlx.stderr b/src/tools/miri/tests/genmc/fail/data_race/weak_orderings.rlx_rlx.stderr index 1220c0c09cbe..e589153e1e81 100644 --- a/src/tools/miri/tests/genmc/fail/data_race/weak_orderings.rlx_rlx.stderr +++ b/src/tools/miri/tests/genmc/fail/data_race/weak_orderings.rlx_rlx.stderr @@ -13,7 +13,7 @@ LL | X = 2; at tests/genmc/fail/data_race/weak_orderings.rs:LL:CC 1: as std::ops::FnOnce<()>>::call_once at RUSTLIB/alloc/src/boxed.rs:LL:CC - 2: genmc::spawn_pthread_closure::thread_func + 2: genmc::spawn_pthread_closure::thread_func::<{closure@tests/genmc/fail/data_race/weak_orderings.rs:LL:CC}> at tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC note: the last function in that backtrace got called indirectly due to this code --> tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC diff --git a/src/tools/miri/tests/genmc/fail/shims/mutex_diff_thread_unlock.stderr b/src/tools/miri/tests/genmc/fail/shims/mutex_diff_thread_unlock.stderr index 272a52ba7fef..9171c07eebcd 100644 --- a/src/tools/miri/tests/genmc/fail/shims/mutex_diff_thread_unlock.stderr +++ b/src/tools/miri/tests/genmc/fail/shims/mutex_diff_thread_unlock.stderr @@ -11,11 +11,11 @@ LL | self.lock.inner.unlock(); = note: stack backtrace: 0: as std::ops::Drop>::drop at RUSTLIB/std/src/sync/poison/mutex.rs:LL:CC - 1: std::ptr::drop_in_place)) + 1: std::ptr::drop_glue::> - shim(Some(std::sync::MutexGuard<'_, u64>)) at RUSTLIB/core/src/ptr/mod.rs:LL:CC - 2: std::ptr::drop_in_place)) + 2: std::ptr::drop_glue::>> - shim(Some(EvilSend>)) at RUSTLIB/core/src/ptr/mod.rs:LL:CC - 3: std::mem::drop + 3: std::mem::drop::>> at RUSTLIB/core/src/mem/mod.rs:LL:CC 4: miri_start::{closure#0} at tests/genmc/fail/shims/mutex_diff_thread_unlock.rs:LL:CC diff --git a/src/tools/miri/tests/genmc/fail/shims/mutex_double_unlock.stderr b/src/tools/miri/tests/genmc/fail/shims/mutex_double_unlock.stderr index d668706f7647..e75eed834cb7 100644 --- a/src/tools/miri/tests/genmc/fail/shims/mutex_double_unlock.stderr +++ b/src/tools/miri/tests/genmc/fail/shims/mutex_double_unlock.stderr @@ -10,9 +10,9 @@ LL | self.lock.inner.unlock(); = note: stack backtrace: 0: as std::ops::Drop>::drop at RUSTLIB/std/src/sync/poison/mutex.rs:LL:CC - 1: std::ptr::drop_in_place)) + 1: std::ptr::drop_glue::> - shim(Some(std::sync::MutexGuard<'_, u64>)) at RUSTLIB/core/src/ptr/mod.rs:LL:CC - 2: std::mem::drop + 2: std::mem::drop::> at RUSTLIB/core/src/mem/mod.rs:LL:CC 3: miri_start at tests/genmc/fail/shims/mutex_double_unlock.rs:LL:CC diff --git a/src/tools/miri/tests/genmc/fail/simple/alloc_large.multiple.stderr b/src/tools/miri/tests/genmc/fail/simple/alloc_large.multiple.stderr index 5ddcce1fa30c..f9d217acf42e 100644 --- a/src/tools/miri/tests/genmc/fail/simple/alloc_large.multiple.stderr +++ b/src/tools/miri/tests/genmc/fail/simple/alloc_large.multiple.stderr @@ -11,11 +11,11 @@ LL | AllocInit::Uninitialized => alloc.allocate(layout), at RUSTLIB/alloc/src/raw_vec/mod.rs:LL:CC 1: alloc::raw_vec::RawVecInner::with_capacity_in at RUSTLIB/alloc/src/raw_vec/mod.rs:LL:CC - 2: alloc::raw_vec::RawVec::with_capacity_in + 2: alloc::raw_vec::RawVec::::with_capacity_in at RUSTLIB/alloc/src/raw_vec/mod.rs:LL:CC - 3: std::vec::Vec::with_capacity_in + 3: std::vec::Vec::::with_capacity_in at RUSTLIB/alloc/src/vec/mod.rs:LL:CC - 4: std::vec::Vec::with_capacity + 4: std::vec::Vec::::with_capacity at RUSTLIB/alloc/src/vec/mod.rs:LL:CC 5: miri_start at tests/genmc/fail/simple/alloc_large.rs:LL:CC diff --git a/src/tools/miri/tests/genmc/fail/simple/alloc_large.single.stderr b/src/tools/miri/tests/genmc/fail/simple/alloc_large.single.stderr index 5ddcce1fa30c..f9d217acf42e 100644 --- a/src/tools/miri/tests/genmc/fail/simple/alloc_large.single.stderr +++ b/src/tools/miri/tests/genmc/fail/simple/alloc_large.single.stderr @@ -11,11 +11,11 @@ LL | AllocInit::Uninitialized => alloc.allocate(layout), at RUSTLIB/alloc/src/raw_vec/mod.rs:LL:CC 1: alloc::raw_vec::RawVecInner::with_capacity_in at RUSTLIB/alloc/src/raw_vec/mod.rs:LL:CC - 2: alloc::raw_vec::RawVec::with_capacity_in + 2: alloc::raw_vec::RawVec::::with_capacity_in at RUSTLIB/alloc/src/raw_vec/mod.rs:LL:CC - 3: std::vec::Vec::with_capacity_in + 3: std::vec::Vec::::with_capacity_in at RUSTLIB/alloc/src/vec/mod.rs:LL:CC - 4: std::vec::Vec::with_capacity + 4: std::vec::Vec::::with_capacity at RUSTLIB/alloc/src/vec/mod.rs:LL:CC 5: miri_start at tests/genmc/fail/simple/alloc_large.rs:LL:CC diff --git a/src/tools/miri/tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.stderr b/src/tools/miri/tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.stderr index 24bde07924d1..720879217679 100644 --- a/src/tools/miri/tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.stderr +++ b/src/tools/miri/tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.stderr @@ -13,7 +13,7 @@ LL | match KEY.compare_exchange(KEY_SENTVAL, key, Release, Acquire) { at tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.rs:LL:CC 2: as std::ops::FnOnce<()>>::call_once at RUSTLIB/alloc/src/boxed.rs:LL:CC - 3: genmc::spawn_pthread_closure::thread_func + 3: genmc::spawn_pthread_closure::thread_func::<{closure@tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.rs:LL:CC}> at tests/genmc/pass/atomics/../../../utils/genmc.rs:LL:CC note: the last function in that backtrace got called indirectly due to this code --> tests/genmc/pass/atomics/../../../utils/genmc.rs:LL:CC diff --git a/src/tools/miri/tests/pass-dep/libc/libc-blocking-io-same-fd.rs b/src/tools/miri/tests/pass-dep/libc/libc-blocking-io-same-fd.rs index 01bda8a4f15f..96af64131f1d 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-blocking-io-same-fd.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-blocking-io-same-fd.rs @@ -24,16 +24,15 @@ fn main() { thread::yield_now(); let mut buffer = [22u8; 128]; - let bytes_written = unsafe { - errno_result(libc_utils::write_all_generic( + unsafe { + libc_utils::write_all_generic( buffer.as_mut_ptr().cast(), buffer.len(), libc_utils::NoRetry, |buf, len| libc::send(peerfd, buf, len, 0), - )) + ) .unwrap() }; - assert_eq!(bytes_written as usize, 128); }); net::connect_ipv4(client_sockfd, addr).unwrap(); @@ -41,12 +40,12 @@ fn main() { let reader_thread = thread::spawn(move || { let mut buffer = [0u8; 8]; unsafe { - errno_result(libc_utils::read_all_generic( + libc_utils::read_exact_generic( buffer.as_mut_ptr().cast(), buffer.len(), libc_utils::NoRetry, |buf, count| libc::recv(client_sockfd, buf, count, 0), - )) + ) .unwrap() }; assert_eq!(&buffer, &[22u8; 8]); @@ -54,12 +53,12 @@ fn main() { let mut buffer = [0u8; 8]; unsafe { - errno_result(libc_utils::read_all_generic( + libc_utils::read_exact_generic( buffer.as_mut_ptr().cast(), buffer.len(), libc_utils::NoRetry, |buf, count| libc::recv(client_sockfd, buf, count, 0), - )) + ) .unwrap() }; assert_eq!(&buffer, &[22u8; 8]); 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 f9615fc6e414..d9a2be7fc501 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 @@ -58,7 +58,7 @@ fn test_epoll_block_then_unblock() { // epoll_wait before triggering notification so it will block then get unblocked before timeout. let thread1 = thread::spawn(move || { thread::yield_now(); - write_all_from_slice(fds[1], b"abcde").unwrap(); + write_all(fds[1], b"abcde").unwrap(); }); check_epoll_wait::<1>(epfd, &[Ev { events: libc::EPOLLIN | libc::EPOLLOUT, data: fds[0] }], 10); thread1.join().unwrap(); @@ -83,7 +83,7 @@ fn test_notification_after_timeout() { check_epoll_wait::<1>(epfd, &[], 10); // Trigger epoll notification after timeout. - write_all_from_slice(fds[1], b"abcde").unwrap(); + write_all(fds[1], b"abcde").unwrap(); // Check the result of the notification. check_epoll_wait::<1>(epfd, &[Ev { events: libc::EPOLLIN | libc::EPOLLOUT, data: fds[0] }], 10); @@ -106,7 +106,7 @@ fn test_epoll_race() { // Write to the static mut variable. unsafe { VAL = 1 }; // Write to the eventfd instance. - write_all_from_slice(fd, &1_u64.to_ne_bytes()).unwrap(); + write_all(fd, &1_u64.to_ne_bytes()).unwrap(); }); thread::yield_now(); // epoll_wait for the event to happen. @@ -130,7 +130,7 @@ fn wakeup_on_new_interest() { errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) }); // Write to fd[0] - write_all_from_slice(fds[0], b"abcde").unwrap(); + write_all(fds[0], b"abcde").unwrap(); // Block a thread on the epoll instance. let t = std::thread::spawn(move || { @@ -189,7 +189,7 @@ fn multiple_events_wake_multiple_threads() { thread::yield_now(); // Trigger the eventfd. This triggers two events at once! - write_all_from_slice(fd1, &0_u64.to_ne_bytes()).unwrap(); + write_all(fd1, &0_u64.to_ne_bytes()).unwrap(); // Both threads should have been woken up so that both events can be consumed. let e1 = t1.join().unwrap(); diff --git a/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs b/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs index 63300c9a433c..e8f54bf85a2d 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs @@ -56,10 +56,10 @@ fn test_epoll_socketpair() { let mut fds = [-1, -1]; errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) }); - // Write to fd[0] - write_all_from_slice(fds[0], b"abcde").unwrap(); + // Write to fds[0] + write_all(fds[0], b"abcde").unwrap(); - // Register fd[1] with EPOLLIN|EPOLLOUT|EPOLLET|EPOLLRDHUP + // Register fds[1] with EPOLLIN|EPOLLOUT|EPOLLET|EPOLLRDHUP epoll_ctl_add(epfd, fds[1], EPOLLIN | EPOLLOUT | EPOLLET | EPOLLRDHUP).unwrap(); // Check result from epoll_wait. @@ -68,10 +68,10 @@ fn test_epoll_socketpair() { // Check that this is indeed using "ET" (edge-trigger) semantics: a second epoll should return nothing. check_epoll_wait_noblock::<8>(epfd, &[]); - // Write some more to fd[0]. - write_all_from_slice(fds[0], b"abcde").unwrap(); + // Write some more to fds[0]. + write_all(fds[0], b"abcde").unwrap(); - // This did not change the readiness of fd[1], so we should get no event. + // This did not change the readiness of fds[1], so we should get no event. // However, Linux seems to always deliver spurious events to the peer on each write, // so we match that. check_epoll_wait_noblock::<8>(epfd, &[Ev { data: fds[1], events: EPOLLIN | EPOLLOUT }]); @@ -98,7 +98,7 @@ fn test_epoll_ctl_mod() { let mut fds = [-1, -1]; errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) }); - // Register fd[1] with EPOLLIN|EPOLLET, and data of "0". + // Register fds[1] with EPOLLIN|EPOLLET, and data of "0". epoll_ctl(epfd, EPOLL_CTL_ADD, fds[1], Ev { events: EPOLLIN | EPOLLET, data: 0 }).unwrap(); // Check result from epoll_wait. No notification would be returned. @@ -112,8 +112,8 @@ fn test_epoll_ctl_mod() { // Write to fds[1] and read from fds[0] to make the notification ready again // (relying on there always being an event when the buffer gets emptied). - write_all_from_slice(fds[1], "abc".as_bytes()).unwrap(); - read_all_into_array::<3>(fds[0]).unwrap(); + write_all(fds[1], "abc".as_bytes()).unwrap(); + read_exact_array::<3>(fds[0]).unwrap(); // Now that the event is already ready, change the "data" value. epoll_ctl(epfd, EPOLL_CTL_MOD, fds[1], Ev { events: EPOLLOUT | EPOLLET, data: 2 }).unwrap(); @@ -136,12 +136,10 @@ fn test_epoll_ctl_del() { let mut fds = [-1, -1]; errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) }); - // Write to fd[0] - let data = b"abcde".as_ptr(); - let res = unsafe { libc_utils::write_all(fds[0], data as *const libc::c_void, 5) }; - assert_eq!(res, 5); + // Write to fds[0] + libc_utils::write_all(fds[0], b"abcde").unwrap(); - // Register fd[1] with EPOLLIN|EPOLLOUT|EPOLLET + // Register fds[1] with EPOLLIN|EPOLLOUT|EPOLLET let mut ev = libc::epoll_event { events: (libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET) as u32, u64: u64::try_from(fds[1]).unwrap(), @@ -168,9 +166,7 @@ fn test_two_epoll_instance() { errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) }); // Write to the socketpair. - let data = b"abcde".as_ptr(); - let res = unsafe { libc_utils::write_all(fds[0], data as *const libc::c_void, 5) }; - assert_eq!(res, 5); + libc_utils::write_all(fds[0], b"abcde").unwrap(); // Register one side of the socketpair with EPOLLIN | EPOLLOUT | EPOLLET. epoll_ctl_add(epfd1, fds[1], libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).unwrap(); @@ -208,9 +204,7 @@ fn test_two_same_fd_in_same_epoll_instance() { assert_eq!(res, 0); // Write to the socketpair. - let data = b"abcde".as_ptr(); - let res = unsafe { libc_utils::write_all(fds[0], data as *const libc::c_void, 5) }; - assert_eq!(res, 5); + libc_utils::write_all(fds[0], b"abcde").unwrap(); // Two notification should be received. let expected_event = u32::try_from(libc::EPOLLIN | libc::EPOLLOUT).unwrap(); @@ -227,7 +221,7 @@ fn test_epoll_eventfd() { let fd = errno_result(unsafe { libc::eventfd(0, flags) }).unwrap(); // Write 1 to the eventfd instance. - libc_utils::write_all_from_slice(fd, &1_u64.to_ne_bytes()).unwrap(); + libc_utils::write_all(fd, &1_u64.to_ne_bytes()).unwrap(); // Create an epoll instance. let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); @@ -241,14 +235,14 @@ fn test_epoll_eventfd() { check_epoll_wait::<8>(epfd, &[(expected_event, expected_value)]); // Write 0 to the eventfd. - libc_utils::write_all_from_slice(fd, &0_u64.to_ne_bytes()).unwrap(); + libc_utils::write_all(fd, &0_u64.to_ne_bytes()).unwrap(); // This does not change the status, so we should get no event. // However, Linux performs a spurious wakeup. check_epoll_wait::<8>(epfd, &[(expected_event, expected_value)]); // Read from the eventfd. - libc_utils::read_all_into_array::<8>(fd).unwrap(); + libc_utils::read_exact_array::<8>(fd).unwrap(); // This consumes the event, so the read status is gone. However, deactivation // does not trigger an event. @@ -257,9 +251,7 @@ fn test_epoll_eventfd() { check_epoll_wait::<8>(epfd, &[(expected_event, expected_value)]); // Write the maximum possible value. - let sized_8_data: [u8; 8] = (u64::MAX - 1).to_ne_bytes(); - let res = unsafe { libc_utils::write_all(fd, sized_8_data.as_ptr() as *const libc::c_void, 8) }; - assert_eq!(res, 8); + libc_utils::write_all(fd, &(u64::MAX - 1).to_ne_bytes()).unwrap(); // This reactivates reads, therefore triggering an event. Writing is no longer possible. let expected_event = u32::try_from(libc::EPOLLIN).unwrap(); @@ -282,9 +274,7 @@ fn test_epoll_socketpair_both_sides() { // Write to fds[1]. // (We do the write after the register here, unlike in `test_epoll_socketpair`, to ensure // we cover both orders in which this could be done.) - let data = b"abcde".as_ptr(); - let res = unsafe { libc_utils::write_all(fds[1], data as *const libc::c_void, 5) }; - assert_eq!(res, 5); + libc_utils::write_all(fds[1], b"abcde").unwrap(); // Two notification should be received. let expected_event0 = u32::try_from(libc::EPOLLIN | libc::EPOLLOUT).unwrap(); @@ -297,10 +287,7 @@ fn test_epoll_socketpair_both_sides() { ); // Read from fds[0]. - let mut buf: [u8; 5] = [0; 5]; - let res = - unsafe { libc_utils::read_all(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) }; - assert_eq!(res, 5); + let buf = libc_utils::read_exact_array::<5>(fds[0]).unwrap(); assert_eq!(buf, *b"abcde"); // The state of fds[1] does not change (was writable, is writable). @@ -323,9 +310,7 @@ fn test_closed_fd() { epoll_ctl_add(epfd, fd, libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).unwrap(); // Write to the eventfd instance. - let sized_8_data: [u8; 8] = 1_u64.to_ne_bytes(); - let res = unsafe { libc_utils::write_all(fd, sized_8_data.as_ptr() as *const libc::c_void, 8) }; - assert_eq!(res, 8); + libc_utils::write_all(fd, &1_u64.to_ne_bytes()).unwrap(); // Close the eventfd. errno_check(unsafe { libc::close(fd) }); @@ -363,10 +348,7 @@ fn test_not_fully_closed_fd() { check_epoll_wait::<1>(epfd, &[(expected_event, expected_value)]); // Write to the eventfd instance to produce notification. - let sized_8_data: [u8; 8] = 1_u64.to_ne_bytes(); - let res = - unsafe { libc_utils::write_all(newfd, sized_8_data.as_ptr() as *const libc::c_void, 8) }; - assert_eq!(res, 8); + libc_utils::write_all(newfd, &1_u64.to_ne_bytes()).unwrap(); // Close the dupped fd. errno_check(unsafe { libc::close(newfd) }); @@ -383,9 +365,7 @@ fn test_event_overwrite() { errno_result(unsafe { libc::eventfd(0, libc::EFD_NONBLOCK | libc::EFD_CLOEXEC) }).unwrap(); // Write to the eventfd instance. - let sized_8_data: [u8; 8] = 1_u64.to_ne_bytes(); - let res = unsafe { libc_utils::write_all(fd, sized_8_data.as_ptr() as *const libc::c_void, 8) }; - assert_eq!(res, 8); + libc_utils::write_all(fd, &1_u64.to_ne_bytes()).unwrap(); // Create an epoll instance. let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); @@ -435,9 +415,7 @@ fn test_socketpair_read() { // Write a bunch of data bytes to fds[1]. let data = [42u8; 1024]; - let res = - unsafe { libc_utils::write_all(fds[1], data.as_ptr() as *const libc::c_void, data.len()) }; - assert_eq!(res, data.len() as isize); + libc_utils::write_all(fds[1], &data).unwrap(); // Two notification should be received. let expected_event0 = u32::try_from(libc::EPOLLIN | libc::EPOLLOUT).unwrap(); @@ -451,9 +429,7 @@ fn test_socketpair_read() { // Read some of the data from fds[0]. let mut buf = [0; 512]; - let res = - unsafe { libc_utils::read_all(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) }; - assert_eq!(res, buf.len() as isize); + libc_utils::read_exact(fds[0], &mut buf).unwrap(); // fds[1] did not change, it is still writable, so we get no event. let expected_event = u32::try_from(libc::EPOLLOUT).unwrap(); @@ -462,9 +438,7 @@ fn test_socketpair_read() { // Read until the buffer is empty. let rest = data.len() - buf.len(); - let res = - unsafe { libc_utils::read_all(fds[0], buf.as_mut_ptr().cast(), rest as libc::size_t) }; - assert_eq!(res, rest as isize); + libc_utils::read_exact(fds[0], &mut buf[..rest]).unwrap(); // Now we get a notification that fds[1] can be written. This is spurious since it // could already be written before, but Linux seems to always emit a notification for @@ -481,7 +455,7 @@ fn test_no_notification_for_unregister_flag() { let mut fds = [-1, -1]; errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) }); - // Register fd[0] with EPOLLOUT|EPOLLET. + // Register fds[0] with EPOLLOUT|EPOLLET. let mut ev = libc::epoll_event { events: (libc::EPOLLOUT | libc::EPOLLET).cast_unsigned(), u64: u64::try_from(fds[0]).unwrap(), @@ -489,12 +463,8 @@ fn test_no_notification_for_unregister_flag() { let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fds[0], &mut ev) }; assert_eq!(res, 0); - // Write to fd[1]. - let data = b"abcde".as_ptr(); - let res: i32 = unsafe { - libc_utils::write_all(fds[1], data as *const libc::c_void, 5).try_into().unwrap() - }; - assert_eq!(res, 5); + // Write to fds[1]. + libc_utils::write_all(fds[1], b"abcde").unwrap(); // Check result from epoll_wait. Since we didn't register EPOLLIN flag, the notification won't // contain EPOLLIN even though fds[0] is now readable. @@ -523,16 +493,14 @@ fn test_socketpair_epollerr() { let mut fds = [-1, -1]; errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) }); - // Write to fd[0] - let data = b"abcde".as_ptr(); - let res = unsafe { libc_utils::write_all(fds[0], data as *const libc::c_void, 5) }; - assert_eq!(res, 5); + // Write to fds[0] + libc_utils::write_all(fds[0], b"abcde").unwrap(); // Close fds[1]. // EPOLLERR will be triggered if we close peer fd that still has data in its read buffer. errno_check(unsafe { libc::close(fds[1]) }); - // Register fd[1] with EPOLLIN|EPOLLOUT|EPOLLET|EPOLLRDHUP + // Register fds[1] with EPOLLIN|EPOLLOUT|EPOLLET|EPOLLRDHUP epoll_ctl_add(epfd, fds[0], libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET | libc::EPOLLRDHUP) .unwrap(); @@ -671,9 +639,7 @@ fn test_issue_3858() { errno_check(unsafe { libc::close(epfd) }); // Write to the eventfd instance. - let sized_8_data: [u8; 8] = 1_u64.to_ne_bytes(); - let res = unsafe { libc_utils::write_all(fd, sized_8_data.as_ptr() as *const libc::c_void, 8) }; - assert_eq!(res, 8); + libc_utils::write_all(fd, &1_u64.to_ne_bytes()).unwrap(); } /// Ensure that if a socket becomes un-writable, we don't see it any more. @@ -694,10 +660,9 @@ fn test_issue_4374() { // Fill up fds[0] so that it is not writable any more. let zeros = [0u8; 512]; loop { - let res = unsafe { - libc_utils::write_all(fds[0], zeros.as_ptr() as *const libc::c_void, zeros.len()) - }; - if res < 0 { + let res = libc_utils::write_all(fds[0], &zeros); + if let Err(err) = res { + assert_eq!(err.kind(), std::io::ErrorKind::WouldBlock); break; } } @@ -719,19 +684,13 @@ fn test_issue_4374_reads() { assert_eq!(unsafe { libc::fcntl(fds[1], libc::F_SETFL, libc::O_NONBLOCK) }, 0); // Write to fds[1] so that fds[0] becomes readable. - let data = b"abcde".as_ptr(); - let res: i32 = unsafe { - libc_utils::write_all(fds[1], data as *const libc::c_void, 5).try_into().unwrap() - }; - assert_eq!(res, 5); + libc_utils::write_all(fds[1], b"abcde").unwrap(); // Register fds[0] with epoll while it is readable. epoll_ctl_add(epfd0, fds[0], libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).unwrap(); // Read fds[0] so it is no longer readable. - let mut buf = [0u8; 512]; - let res = unsafe { libc_utils::read_all(fds[0], buf.as_mut_ptr() as *mut libc::c_void, 5) }; - assert_eq!(res, 5); + libc_utils::read_exact_array::<5>(fds[0]).unwrap(); // We should now still see a notification, but only about it being writable. let expected_event = u32::try_from(libc::EPOLLOUT).unwrap(); 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 new file mode 100644 index 000000000000..f03e1ad8641d --- /dev/null +++ b/src/tools/miri/tests/pass-dep/libc/libc-fs-permissions.rs @@ -0,0 +1,50 @@ +//@ignore-target: windows # no libc +//@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; + +#[path = "../../utils/mod.rs"] +mod utils; + +#[path = "../../utils/libc.rs"] +mod libc_utils; +use libc_utils::{errno_check, errno_result}; + +fn main() { + test_chmod(); + test_fchmod(); +} + +#[track_caller] +fn getmod(path: &CStr) -> u32 { + let mut stat = MaybeUninit::::uninit(); + unsafe { errno_check(libc::stat(path.as_ptr(), stat.as_mut_ptr())) }; + u32::from(unsafe { stat.assume_init_ref().st_mode & !libc::S_IFMT }) +} + +fn test_chmod() { + let path = utils::prepare_with_content("miri_test_libc_chmod.txt", b"abcdef"); + let c_path = CString::new(path.as_os_str().as_bytes()).expect("CString::new failed"); + + unsafe { errno_check(libc::chmod(c_path.as_ptr(), 0o777)) }; + assert_eq!(getmod(&c_path), 0o777); + unsafe { errno_check(libc::chmod(c_path.as_ptr(), 0o610)) }; + assert_eq!(getmod(&c_path), 0o610); +} + +fn test_fchmod() { + let path = utils::prepare_with_content("miri_test_libc_chmod.txt", b"abcdef"); + let c_path = CString::new(path.as_os_str().as_bytes()).expect("CString::new failed"); + + let fd = unsafe { errno_result(libc::open(c_path.as_ptr(), libc::O_RDONLY)).unwrap() }; + unsafe { errno_check(libc::fchmod(fd, 0o777)) }; + assert_eq!(getmod(&c_path), 0o777); + unsafe { errno_check(libc::fchmod(fd, 0o610)) }; + assert_eq!(getmod(&c_path), 0o610); +} 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 b2402ac482ff..a388943f922f 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs @@ -221,8 +221,8 @@ fn test_dup_stdout_stderr() { unsafe { let new_stdout = libc::fcntl(1, libc::F_DUPFD, 0); let new_stderr = libc::fcntl(2, libc::F_DUPFD, 0); - libc_utils::write_all(new_stdout, bytes.as_ptr() as *const libc::c_void, bytes.len()); - libc_utils::write_all(new_stderr, bytes.as_ptr() as *const libc::c_void, bytes.len()); + libc_utils::write_all(new_stdout, bytes).unwrap(); + libc_utils::write_all(new_stderr, bytes).unwrap(); } } @@ -605,6 +605,7 @@ fn test_fstat() { assert_eq!(stat.st_size, 5); assert_eq!(stat.st_mode & libc::S_IFMT, libc::S_IFREG); + assert_ne!(stat.st_mode & !libc::S_IFMT, 0, "some permission should be set"); // Check that all fields are initialized. check_stat_fields(stat); @@ -625,6 +626,7 @@ fn test_stat() { assert_eq!(stat.st_size, 5); assert_eq!(stat.st_mode & libc::S_IFMT, libc::S_IFREG); + assert_ne!(stat.st_mode & !libc::S_IFMT, 0, "some permission should be set"); // Check that all fields are initialized. check_stat_fields(stat); @@ -648,6 +650,7 @@ fn test_lstat() { let stat = unsafe { stat.assume_init_ref() }; assert_eq!(stat.st_mode & libc::S_IFMT, libc::S_IFLNK); + assert_ne!(stat.st_mode & !libc::S_IFMT, 0, "some permission should be set"); // Check that all fields are initialized. check_stat_fields(stat); diff --git a/src/tools/miri/tests/pass-dep/libc/libc-fstat-non-file.rs b/src/tools/miri/tests/pass-dep/libc/libc-fstat-non-file.rs index c29c8ceaa1df..cf848f190331 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-fstat-non-file.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-fstat-non-file.rs @@ -55,6 +55,7 @@ fn test_fstat_socketpair() { libc::S_IFSOCK, "socketpair should have S_IFSOCK mode" ); + assert_ne!(stat.st_mode & !libc::S_IFMT, 0, "socketpair should have permissions"); assert_eq!(stat.st_size, 0, "socketpair should have size 0"); assert_stat_fields_are_accessible(stat); } @@ -72,6 +73,7 @@ fn test_fstat_pipe() { let mut buf = MaybeUninit::uninit(); let stat = do_fstat(*fd, &mut buf); assert_eq!(stat.st_mode & libc::S_IFMT, libc::S_IFIFO, "pipe should have S_IFIFO mode"); + assert_ne!(stat.st_mode & !libc::S_IFMT, 0, "pipe should have permissions"); assert_eq!(stat.st_size, 0, "pipe should have size 0"); assert_stat_fields_are_accessible(stat); } diff --git a/src/tools/miri/tests/pass-dep/libc/libc-pipe.rs b/src/tools/miri/tests/pass-dep/libc/libc-pipe.rs index 8f8d4f85c010..98d7340fa9db 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-pipe.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-pipe.rs @@ -31,19 +31,19 @@ fn test_pipe() { // Read size == data available in buffer. let data = b"12345"; - write_all_from_slice(fds[1], data).unwrap(); - let buf3 = read_all_into_array::<5>(fds[0]).unwrap(); + write_all(fds[1], data).unwrap(); + let buf3 = read_exact_array::<5>(fds[0]).unwrap(); assert_eq!(&buf3, data); // Read size > data available in buffer. let data = b"123"; - write_all_from_slice(fds[1], data).unwrap(); + write_all(fds[1], data).unwrap(); let mut buf4: [u8; 5] = [0; 5]; - let (part1, rest) = read_into_slice(fds[0], &mut buf4).unwrap(); + let (part1, rest) = read_split_slice(fds[0], &mut buf4).unwrap(); assert_eq!(part1[..], data[..part1.len()]); // Write 2 more bytes so we can exactly fill the `rest`. - write_all_from_slice(fds[1], b"34").unwrap(); - read_all_into_slice(fds[0], rest).unwrap(); + write_all(fds[1], b"34").unwrap(); + read_exact(fds[0], rest).unwrap(); } fn test_pipe_threaded() { @@ -51,19 +51,19 @@ fn test_pipe_threaded() { errno_check(unsafe { libc::pipe(fds.as_mut_ptr()) }); let thread1 = thread::spawn(move || { - let buf = read_all_into_array::<5>(fds[0]).unwrap(); + let buf = read_exact_array::<5>(fds[0]).unwrap(); assert_eq!(&buf, b"abcde"); }); thread::yield_now(); - write_all_from_slice(fds[1], b"abcde").unwrap(); + write_all(fds[1], b"abcde").unwrap(); thread1.join().unwrap(); // Read and write from different direction let thread2 = thread::spawn(move || { thread::yield_now(); - write_all_from_slice(fds[1], b"12345").unwrap(); + write_all(fds[1], b"12345").unwrap(); }); - let buf = read_all_into_array::<5>(fds[0]).unwrap(); + let buf = read_exact_array::<5>(fds[0]).unwrap(); assert_eq!(&buf, b"12345"); thread2.join().unwrap(); } @@ -77,13 +77,13 @@ fn test_race() { let thread1 = thread::spawn(move || { // write() from the main thread will occur before the read() here // because preemption is disabled and the main thread yields after write(). - let buf = read_all_into_array::<1>(fds[0]).unwrap(); + let buf = read_exact_array::<1>(fds[0]).unwrap(); assert_eq!(&buf, b"a"); // The read above establishes a happens-before so it is now safe to access this global variable. unsafe { assert_eq!(VAL, 1) }; }); unsafe { VAL = 1 }; - write_all_from_slice(fds[1], b"a").unwrap(); + write_all(fds[1], b"a").unwrap(); thread::yield_now(); thread1.join().unwrap(); } @@ -190,10 +190,10 @@ fn test_pipe_fcntl_threaded() { // The write below will unblock the `read` in main thread: even though // the socket is now "non-blocking", the shim needs to deal correctly // with threads that were blocked before the socket was made non-blocking. - write_all_from_slice(fds[1], b"abcde").unwrap(); + write_all(fds[1], b"abcde").unwrap(); }); // The `read` below will block. - let buf = read_all_into_array::<5>(fds[0]).unwrap(); + let buf = read_exact_array::<5>(fds[0]).unwrap(); thread1.join().unwrap(); assert_eq!(&buf, b"abcde"); } diff --git a/src/tools/miri/tests/pass-dep/libc/libc-socket-address.rs b/src/tools/miri/tests/pass-dep/libc/libc-socket-address.rs new file mode 100644 index 000000000000..30ba4414be0c --- /dev/null +++ b/src/tools/miri/tests/pass-dep/libc/libc-socket-address.rs @@ -0,0 +1,88 @@ +//@ignore-target: windows # No libc socket on Windows +//@compile-flags: -Zmiri-disable-isolation + +#[path = "../../utils/libc.rs"] +mod libc_utils; +#[path = "../../utils/mod.rs"] +mod utils; + +use std::ffi::CString; +use std::ptr; + +use libc_utils::*; + +fn main() { + test_getaddrinfo_freeaddrinfo(); +} + +/// Test doing address resolution using the `getaddrinfo` syscall. +/// This also tests freeing the address linked list using `freeaddrinfo`. +fn test_getaddrinfo_freeaddrinfo() { + let node_c_str = CString::new("localhost").unwrap(); + let service_c_str = CString::new("8080").unwrap(); + + let mut hints: libc::addrinfo = unsafe { std::mem::zeroed() }; + hints.ai_socktype = libc::SOCK_STREAM; + let mut res: *mut libc::addrinfo = ptr::null_mut(); + unsafe { + errno_check(libc::getaddrinfo( + node_c_str.as_ptr(), + service_c_str.as_ptr(), + &hints, + &mut res, + )); + } + let start = res; + let mut addr_count = 0; + + loop { + unsafe { + let Some(cur) = res.as_ref() else { + // It's a null pointer so we're at the end of the linked list. + break; + }; + + addr_count += 1; + match (*cur).ai_family as libc::c_int { + libc::AF_INET => { + let (_, addr) = net::sockname_ipv4(|storage, len| { + *(storage as *mut libc::sockaddr_in) = *cur.ai_addr.cast(); + *len = (*res).ai_addrlen; + 0 + }) + .unwrap(); + + let localhost_ipv4 = net::sock_addr_ipv4(net::IPV4_LOCALHOST, 8080); + assert_eq!(localhost_ipv4.sin_family, addr.sin_family); + assert_eq!(localhost_ipv4.sin_port, addr.sin_port); + assert_eq!(localhost_ipv4.sin_addr.s_addr, addr.sin_addr.s_addr); + } + libc::AF_INET6 => { + let (_, addr) = net::sockname_ipv6(|storage, len| { + *(storage as *mut libc::sockaddr_in6) = *cur.ai_addr.cast(); + *len = (*res).ai_addrlen; + 0 + }) + .unwrap(); + + let localhost_ipv6 = net::sock_addr_ipv6(net::IPV6_LOCALHOST, 8080); + assert_eq!(localhost_ipv6.sin6_family, addr.sin6_family); + assert_eq!(localhost_ipv6.sin6_port, addr.sin6_port); + assert_eq!(localhost_ipv6.sin6_flowinfo, addr.sin6_flowinfo); + assert_eq!(localhost_ipv6.sin6_scope_id, addr.sin6_scope_id); + assert_eq!(localhost_ipv6.sin6_addr.s6_addr, addr.sin6_addr.s6_addr); + } + family => panic!("unexpected address family: {family}"), + } + + res = cur.ai_next; + } + } + + // We expect an IPv4 and an IPv6 address. + assert!(addr_count == 2); + + unsafe { + libc::freeaddrinfo(start.cast()); + } +} diff --git a/src/tools/miri/tests/pass-dep/libc/libc-socket-invalid-addr.rs b/src/tools/miri/tests/pass-dep/libc/libc-socket-invalid-addr.rs new file mode 100644 index 000000000000..4e46870da204 --- /dev/null +++ b/src/tools/miri/tests/pass-dep/libc/libc-socket-invalid-addr.rs @@ -0,0 +1,31 @@ +//@ignore-target: windows # No libc socket on Windows +//@compile-flags: -Zmiri-disable-isolation +//@normalize-stderr-test: "address resolution failed: .*" -> "address resolution failed: $$MSG" + +#[path = "../../utils/libc.rs"] +mod libc_utils; +#[path = "../../utils/mod.rs"] +mod utils; + +use std::ffi::CString; +use std::ptr; + +use libc_utils::*; + +/// Test doing address resolution using the `getaddrinfo` syscall. +/// This also tests freeing the address linked list using `freeaddrinfo`. +fn main() { + let node_c_str = CString::new("this-is-not-a-valid-address").unwrap(); + let service_c_str = CString::new("8080").unwrap(); + + let mut hints: libc::addrinfo = unsafe { std::mem::zeroed() }; + hints.ai_socktype = libc::SOCK_STREAM; + let mut res: *mut libc::addrinfo = ptr::null_mut(); + let retval = + unsafe { libc::getaddrinfo(node_c_str.as_ptr(), service_c_str.as_ptr(), &hints, &mut res) }; + + // We return a system error. + assert_eq!(retval, libc::EAI_SYSTEM); + // Last error should be a generic protocol error. + assert_eq!(errno(), libc::EPROTO); +} diff --git a/src/tools/miri/tests/pass-dep/libc/libc-socket-invalid-addr.stderr b/src/tools/miri/tests/pass-dep/libc/libc-socket-invalid-addr.stderr new file mode 100644 index 000000000000..4eea2b6d24fb --- /dev/null +++ b/src/tools/miri/tests/pass-dep/libc/libc-socket-invalid-addr.stderr @@ -0,0 +1,8 @@ +warning: address resolution failed: $MSG + --> tests/pass-dep/libc/libc-socket-invalid-addr.rs:LL:CC + | +LL | unsafe { libc::getaddrinfo(node_c_str.as_ptr(), service_c_str.as_ptr(), &hints, &mut res) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error during address resolution + | + = note: Miri cannot return proper error information from this call; only a generic error code is being returned + 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 new file mode 100644 index 000000000000..c404c942f2c8 --- /dev/null +++ b/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking-epoll.rs @@ -0,0 +1,342 @@ +//@only-target: linux android illumos +//@compile-flags: -Zmiri-disable-isolation +//@revisions: windows_host unix_host +//@[unix_host] ignore-host: windows +//@[windows_host] only-host: windows + +#![feature(io_error_inprogress)] + +#[path = "../../utils/libc.rs"] +mod libc_utils; + +use std::io::ErrorKind; +use std::thread; +use std::time::Duration; + +use libc_utils::epoll::*; +use libc_utils::*; + +const TEST_BYTES: &[u8] = b"these are some test bytes!"; + +fn main() { + test_connect_nonblock(); + test_accept_nonblock(); + test_connect_nonblock_err(); + test_recv_nonblock(); + #[cfg(not(windows_hosts))] + test_send_nonblock(); +} + +/// Test that connecting to a server socket works when the client +/// socket is non-blocking before the `connect` call. +/// Instead of busy waiting until we no longer get ENOTCONN, we register +/// the client socket to epoll and wait for a WRITABLE event. +fn test_connect_nonblock() { + 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() }; + 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)); + } + + // Spawn the server thread. + let server_thread = thread::spawn(move || { + // Yield to client thread to ensure that it actually needs + // to wait until the connection gets accepted. + thread::sleep(Duration::from_millis(10)); + + net::accept_ipv4(server_sockfd).unwrap(); + }); + + // Non-blocking connects always "fail" with EINPROGRESS. + let err = net::connect_ipv4(client_sockfd, addr).unwrap_err(); + assert_eq!(err.kind(), ErrorKind::InProgress); + + // Add client socket with WRITABLE interest to epoll. + epoll_ctl_add(epfd, client_sockfd, EPOLLOUT | EPOLLET | EPOLLERR).unwrap(); + + // Wait until we are done connecting. + check_epoll_wait::<8>(epfd, &[Ev { events: EPOLLOUT, data: client_sockfd }], -1); + + // There should be no error during async connection. + let errno = + net::getsockopt::(client_sockfd, libc::SOL_SOCKET, libc::SO_ERROR).unwrap(); + assert_eq!(errno, 0); + + // We should now be connected and thus getting the peer name should work. + net::sockname_ipv4(|storage, len| unsafe { libc::getpeername(client_sockfd, storage, len) }) + .unwrap(); + + server_thread.join().unwrap(); +} + +/// Test that accepting incoming connections works with non-blocking server sockets. +/// Instead of busy waiting until we no longer get EWOULDBLOCK, we add the +/// server socket to an epoll instance and wait for a READABLE event. +fn test_accept_nonblock() { + 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() }; + let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); + + unsafe { + // Change server socket to be non-blocking. + errno_check(libc::fcntl(server_sockfd, libc::F_SETFL, libc::O_NONBLOCK)); + } + + // Spawn the server thread. + let server_thread = thread::spawn(move || { + // Add client socket with READABLE interest to epoll. + epoll_ctl_add(epfd, server_sockfd, EPOLLIN | EPOLLET | EPOLLERR).unwrap(); + + // Wait until we get a readable event on the server socket. + check_epoll_wait::<8>(epfd, &[Ev { events: EPOLLIN, data: server_sockfd }], -1); + + // Accepting should now be possible. + net::accept_ipv4(server_sockfd).unwrap(); + + // Accepting should now trigger an EWOULDBLOCK. + let err = net::accept_ipv4(server_sockfd).unwrap_err(); + assert_eq!(err.kind(), ErrorKind::WouldBlock); + + // Ensure that the server is no longer readable because we just + // attempted to accept without an incoming connection. + assert_eq!(current_epoll_readiness::<8>(server_sockfd, EPOLLIN | EPOLLET), 0); + }); + + // Yield to server thread such that it actually needs to wait for an + // incoming client connection. + thread::sleep(Duration::from_millis(10)); + + net::connect_ipv4(client_sockfd, addr).unwrap(); + + server_thread.join().unwrap(); +} + +/// Test that the SO_ERROR socket option is set when attempting to +/// connect to an unbound address without blocking. +fn test_connect_nonblock_err() { + 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); +} + +/// Test receiving bytes from a connected stream without blocking. +/// Instead of busy waiting until we no longer receive EWOULDBLOCK when trying to +/// read from the client, we register the client socket to epoll and wait for +/// READABLE events. +fn test_recv_nonblock() { + 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() }; + let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); + + // Spawn the server thread. + let server_thread = thread::spawn(move || { + let (peerfd, _) = net::accept_ipv4(server_sockfd).unwrap(); + // `peerfd` is a blocking socket now. But that's okay, the client still does non-blocking + // reads/writes. + + // Yield back to client so that it starts receiving before we start sending. + thread::sleep(Duration::from_millis(10)); + + libc_utils::write_all(peerfd, TEST_BYTES).unwrap(); + }); + + net::connect_ipv4(client_sockfd, addr).unwrap(); + + unsafe { + // Change client socket to be non-blocking. + errno_check(libc::fcntl(client_sockfd, libc::F_SETFL, libc::O_NONBLOCK)); + } + + // We are connected and the server socket is not writing. + + let mut buffer = [0; TEST_BYTES.len()]; + // Receiving from a socket when the peer is not writing is + // not possible without blocking. + let err = unsafe { + errno_result(libc::recv(client_sockfd, buffer.as_mut_ptr().cast(), buffer.len(), 0)) + .unwrap_err() + }; + assert_eq!(err.kind(), ErrorKind::WouldBlock); + + // Try to receive bytes from the peer socket without blocking. + // Since the peer socket might do partial writes, we might need to + // call `epoll_wait` multiple times until we received everything. + + // Add client socket with READABLE interest to epoll. + epoll_ctl_add(epfd, client_sockfd, EPOLLIN | EPOLLET | EPOLLERR).unwrap(); + + let mut bytes_received = 0; + + while bytes_received != buffer.len() { + let read_result = unsafe { + errno_result(libc::recv( + client_sockfd, + buffer.as_mut_ptr().byte_add(bytes_received).cast(), + buffer.len() - bytes_received, + 0, + )) + }; + + match read_result { + Ok(received) => bytes_received += received as usize, + Err(err) if err.kind() == ErrorKind::WouldBlock => { + // Use epoll to block until there's data available again. + check_epoll_wait::<8>(epfd, &[Ev { events: EPOLLIN, data: client_sockfd }], -1); + } + Err(err) => panic!("unexpected error whilst receiving: {err}"), + } + } + + assert_eq!(&buffer, TEST_BYTES); + + let mut buffer = [0u8; 1]; + let err = unsafe { + errno_result(libc::recv(client_sockfd, buffer.as_mut_ptr().cast(), buffer.len(), 0)) + .unwrap_err() + }; + assert_eq!(err.kind(), ErrorKind::WouldBlock); + + // Ensure that the client is no longer readable because + // we just got an EWOULDBLOCK from a receive call. + assert_eq!(current_epoll_readiness::<8>(client_sockfd, EPOLLIN | EPOLLET), 0); + + server_thread.join().unwrap(); +} + +/// Test sending bytes into a connected stream without blocking. +/// Once the buffer is filled we wait for the epoll WRITABLE +/// readiness instead of busy waiting until we can +/// write again. +/// +/// **Note**: This can only be tested on UNIX hosts because +/// the socket write buffers dynamically grow for localhost +/// connections on Windows. +#[cfg(not(windows_hosts))] +fn test_send_nonblock() { + 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() }; + let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); + + // Spawn the server thread. + let server_thread = thread::spawn(move || { + let (peerfd, _) = net::accept_ipv4(server_sockfd).unwrap(); + peerfd + }); + + net::connect_ipv4(client_sockfd, addr).unwrap(); + + unsafe { + // Change client socket to be non-blocking. + errno_check(libc::fcntl(client_sockfd, libc::F_SETFL, libc::O_NONBLOCK)); + } + + // Try to send bytes to the peer socket without blocking. + // We first fill up the buffer and ensure that we keep the + // writable readiness during the fill up process. + + // Add client socket with READABLE interest to epoll. + epoll_ctl_add(epfd, client_sockfd, EPOLLOUT | EPOLLET | EPOLLERR).unwrap(); + + let fill_buf = [1u8; 32_000]; + let mut total_written = 0usize; + loop { + // Ensure the socket is still writable. + assert_eq!(current_epoll_readiness::<8>(client_sockfd, EPOLLOUT | EPOLLET), EPOLLOUT); + + let result = unsafe { + errno_result(libc::send(client_sockfd, fill_buf.as_ptr().cast(), fill_buf.len(), 0)) + }; + + match result { + Ok(written) => total_written += written as usize, + Err(err) if err.kind() == ErrorKind::WouldBlock => break, + Err(err) => panic!("unexpected error whilst filling up buffer: {err}"), + } + } + + // The buffer should be filled up because we received an EWOULDBLOCK. + // This also means that the client socket should no longer be writable. + assert_eq!(current_epoll_readiness::<8>(client_sockfd, EPOLLOUT | EPOLLET), 0); + + let peerfd = server_thread.join().unwrap(); + + // Spawn the reader thread. + let reader_thread = thread::spawn(move || { + // Read half of the bytes needed to fill the buffer. + // This should make the client buffer writable again. + // We need to do it this way because different hosts + // have different read thresholds after which they + // trigger a new writable event. + let mut buffer = Vec::with_capacity(total_written / 2); + buffer.fill(0u8); + unsafe { + libc_utils::read_exact_generic( + buffer.as_mut_ptr().cast(), + total_written / 2, + libc_utils::NoRetry, + |buf, count| libc::recv(peerfd, buf, count, 0), + ) + .unwrap() + }; + }); + + // Wait until the socket is again writable. + check_epoll_wait::<8>(epfd, &[Ev { events: EPOLLOUT, data: client_sockfd }], -1); + + let fill_buf = [1u8; 100]; + // We should be able to write again without blocking because we just received + // a writable event. + unsafe { + errno_result(libc::send(client_sockfd, fill_buf.as_ptr().cast(), fill_buf.len(), 0)) + .unwrap() + }; + + reader_thread.join().unwrap(); +} diff --git a/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking.rs b/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking.rs index f43847733bc7..4ae7436cac65 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking.rs @@ -51,6 +51,8 @@ fn main() { test_send_recv_dontwait(); test_write_read_nonblock(); + test_getsockname_ipv4_connect_nonblock(); + test_getpeername_ipv4_nonblock(); test_getpeername_ipv4_nonblock_no_peer(); } @@ -225,6 +227,11 @@ fn test_connect_nonblock() { assert_eq!(err.kind(), ErrorKind::InProgress); loop { + // There should be no error during async connection. + let errno = net::getsockopt::(client_sockfd, libc::SOL_SOCKET, libc::SO_ERROR) + .unwrap(); + assert_eq!(errno, 0); + let result = net::sockname_ipv4(|storage, len| unsafe { libc::getpeername(client_sockfd, storage, len) }); @@ -260,12 +267,12 @@ fn test_send_recv_nonblock() { thread::sleep(Duration::from_millis(10)); unsafe { - errno_result(libc_utils::write_all_generic( + libc_utils::write_all_generic( TEST_BYTES.as_ptr().cast(), TEST_BYTES.len(), libc_utils::NoRetry, |buf, count| libc::send(peerfd, buf, count, 0), - )) + ) .unwrap() }; @@ -273,12 +280,12 @@ fn test_send_recv_nonblock() { // This will block until the client sent us this data. let mut buffer = [0; TEST_BYTES.len()]; unsafe { - errno_result(libc_utils::read_all_generic( + libc_utils::read_exact_generic( buffer.as_mut_ptr().cast(), buffer.len(), libc_utils::NoRetry, |buf, count| libc::recv(peerfd, buf, count, 0), - )) + ) .unwrap() }; assert_eq!(&buffer, TEST_BYTES); @@ -307,12 +314,12 @@ fn test_send_recv_nonblock() { // sleep multiple times until we received everything. unsafe { - errno_result(libc_utils::read_all_generic( + libc_utils::read_exact_generic( buffer.as_mut_ptr().cast(), buffer.len(), libc_utils::RetryAfter(Duration::from_millis(10)), |buf, count| libc::recv(client_sockfd, buf, count, 0), - )) + ) .unwrap() }; assert_eq!(&buffer, TEST_BYTES); @@ -321,12 +328,12 @@ fn test_send_recv_nonblock() { // Sending into the empty buffer should succeed without blocking. unsafe { - errno_result(libc_utils::write_all_generic( + libc_utils::write_all_generic( TEST_BYTES.as_ptr().cast(), TEST_BYTES.len(), libc_utils::NoRetry, |buf, count| libc::send(client_sockfd, buf, count, 0), - )) + ) .unwrap() }; @@ -338,12 +345,12 @@ fn test_send_recv_nonblock() { let fill_buf = [1u8; 5_000_000]; // This fills the socket receive buffer and thus should start blocking. let err = unsafe { - errno_result(libc_utils::write_all_generic( + libc_utils::write_all_generic( fill_buf.as_ptr().cast(), fill_buf.len(), libc_utils::NoRetry, |buf, count| libc::send(client_sockfd, buf, count, 0), - )) + ) .unwrap_err() }; assert_eq!(err.kind(), ErrorKind::WouldBlock) @@ -376,12 +383,12 @@ fn test_send_recv_dontwait() { thread::sleep(Duration::from_millis(10)); unsafe { - errno_result(libc_utils::write_all_generic( + libc_utils::write_all_generic( TEST_BYTES.as_ptr().cast(), TEST_BYTES.len(), libc_utils::NoRetry, |buf, count| libc::send(peerfd, buf, count, 0), - )) + ) .unwrap() }; @@ -389,12 +396,12 @@ fn test_send_recv_dontwait() { // This will block until the client sent us this data. let mut buffer = [0; TEST_BYTES.len()]; unsafe { - errno_result(libc_utils::read_all_generic( + libc_utils::read_exact_generic( buffer.as_mut_ptr().cast(), buffer.len(), libc_utils::NoRetry, |buf, count| libc::recv(peerfd, buf, count, 0), - )) + ) .unwrap() }; assert_eq!(&buffer, TEST_BYTES); @@ -423,12 +430,12 @@ fn test_send_recv_dontwait() { // sleep multiple times until we received everything. unsafe { - errno_result(libc_utils::read_all_generic( + libc_utils::read_exact_generic( buffer.as_mut_ptr().cast(), buffer.len(), libc_utils::RetryAfter(Duration::from_millis(10)), |buf, count| libc::recv(client_sockfd, buf, count, libc::MSG_DONTWAIT), - )) + ) .unwrap() }; assert_eq!(&buffer, TEST_BYTES); @@ -437,12 +444,12 @@ fn test_send_recv_dontwait() { // Sending into the empty buffer should succeed without blocking. unsafe { - errno_result(libc_utils::write_all_generic( + libc_utils::write_all_generic( TEST_BYTES.as_ptr().cast(), TEST_BYTES.len(), libc_utils::NoRetry, |buf, count| libc::send(client_sockfd, buf, count, libc::MSG_DONTWAIT), - )) + ) .unwrap() }; @@ -454,12 +461,12 @@ fn test_send_recv_dontwait() { let fill_buf = [1u8; 5_000_000]; // This fills the socket receive buffer and thus should start blocking. let err = unsafe { - errno_result(libc_utils::write_all_generic( + libc_utils::write_all_generic( fill_buf.as_ptr().cast(), fill_buf.len(), libc_utils::NoRetry, |buf, count| libc::send(client_sockfd, buf, count, libc::MSG_DONTWAIT), - )) + ) .unwrap_err() }; assert_eq!(err.kind(), ErrorKind::WouldBlock) @@ -482,23 +489,12 @@ fn test_write_read_nonblock() { // Yield back to client so that it starts receiving before we start sending. thread::sleep(Duration::from_millis(10)); - let bytes_written = unsafe { - errno_result(libc_utils::write_all( - peerfd, - TEST_BYTES.as_ptr().cast(), - TEST_BYTES.len(), - )) - .unwrap() - }; - assert_eq!(bytes_written as usize, TEST_BYTES.len()); + libc_utils::write_all(peerfd, TEST_BYTES).unwrap(); // The buffer should contain `TEST_BYTES` at the beginning. // This will block until the client sent us this data. let mut buffer = [0; TEST_BYTES.len()]; - unsafe { - errno_result(libc_utils::read_all(peerfd, buffer.as_mut_ptr().cast(), buffer.len())) - .unwrap() - }; + libc_utils::read_exact(peerfd, &mut buffer).unwrap(); assert_eq!(&buffer, TEST_BYTES); }); @@ -529,12 +525,12 @@ fn test_write_read_nonblock() { // sleep multiple times until we read everything. unsafe { - errno_result(libc_utils::read_all_generic( + libc_utils::read_exact_generic( buffer.as_mut_ptr().cast(), buffer.len(), libc_utils::RetryAfter(Duration::from_millis(10)), |buf, count| libc::read(client_sockfd, buf, count), - )) + ) .unwrap() }; assert_eq!(&buffer, TEST_BYTES); @@ -542,15 +538,7 @@ fn test_write_read_nonblock() { // Now we test non-blocking writing. // Writing into the empty buffer should succeed without blocking. - let bytes_written = unsafe { - errno_result(libc_utils::write_all( - client_sockfd, - TEST_BYTES.as_ptr().cast(), - TEST_BYTES.len(), - )) - .unwrap() - }; - assert_eq!(bytes_written as usize, TEST_BYTES.len()); + libc_utils::write_all(client_sockfd, TEST_BYTES).unwrap(); if !cfg!(windows_host) { // Keep sending data until the buffer is full and we block. @@ -560,12 +548,12 @@ fn test_write_read_nonblock() { let fill_buf = [1u8; 5_000_000]; // This fills the socket receive buffer and thus should start blocking. let err = unsafe { - errno_result(libc_utils::write_all_generic( + libc_utils::write_all_generic( fill_buf.as_ptr().cast(), fill_buf.len(), libc_utils::NoRetry, |buf, count| libc::write(client_sockfd, buf, count), - )) + ) .unwrap_err() }; assert_eq!(err.kind(), ErrorKind::WouldBlock) @@ -574,6 +562,52 @@ fn test_write_read_nonblock() { server_thread.join().unwrap(); } +/// Test the `getsockname` syscall on a connecting IPv4 socket +/// which is not connected. +fn test_getsockname_ipv4_connect_nonblock() { + let client_sockfd = + unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 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 `192.0.2.1` we know that nothing is + // listening because it's a blackhole address: + // + // 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([192, 0, 2, 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); + + let (_, sock_addr) = net::sockname_ipv4(|storage, len| unsafe { + libc::getsockname(client_sockfd, storage, len) + }) + .unwrap(); + + // The unspecified IPv4 address. + let addr = net::sock_addr_ipv4([0, 0, 0, 0], 0); + + assert_eq!(addr.sin_family, sock_addr.sin_family); + if cfg!(windows_host) { + // On Windows hosts a connecting socket is bound to the unspecified address. + assert_eq!(addr.sin_addr.s_addr, sock_addr.sin_addr.s_addr); + } else { + // On UNIX hosts a connecting socket is bound to any local interface address + // but not the unspecified address. + assert_ne!(addr.sin_addr.s_addr, sock_addr.sin_addr.s_addr); + } + assert!(sock_addr.sin_port > 0); +} + /// Test that the `getpeername` syscall successfully returns the peer address /// for a non-blocking IPv4 socket whose connection has been successfully /// established before calling the syscall. @@ -651,6 +685,11 @@ fn test_getpeername_ipv4_nonblock_no_peer() { let err = net::connect_ipv4(client_sockfd, addr).unwrap_err(); assert_eq!(err.kind(), ErrorKind::InProgress); + // There should be no error during async connection. + let errno = + net::getsockopt::(client_sockfd, libc::SOL_SOCKET, libc::SO_ERROR).unwrap(); + assert_eq!(errno, 0); + // Since we're never accepting the connection, the socket should never be // successfully connected and thus we should be unable to read the peername. let Err(err) = net::sockname_ipv4(|storage, len| unsafe { diff --git a/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking.windows_host.stderr b/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking.windows_host.stderr new file mode 100644 index 000000000000..006e911499a8 --- /dev/null +++ b/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking.windows_host.stderr @@ -0,0 +1,21 @@ +warning: connecting sockets return unspecified socket addresses on Windows hosts + --> tests/pass-dep/libc/libc-socket-no-blocking.rs:LL:CC + | +LL | libc::getsockname(client_sockfd, storage, len) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Called `getsockname` on connecting socket + | + = note: Windows hosts do not provide `local_addr` information while the socket is still connecting, which might break the assumptions of code compiled for Unix targets + = note: an unspecified socket address (e.g. `0.0.0.0:0`) will be returned instead + = note: this is on thread `main` + = note: stack backtrace: + 0: test_getsockname_ipv4_connect_nonblock::{closure#0} + at tests/pass-dep/libc/libc-socket-no-blocking.rs:LL:CC + 1: libc_utils::net::sockname::<{closure@tests/pass-dep/libc/libc-socket-no-blocking.rs:LL:CC}> + at tests/pass-dep/libc/../../utils/libc.rs:LL:CC + 2: libc_utils::net::sockname_ipv4::<{closure@tests/pass-dep/libc/libc-socket-no-blocking.rs:LL:CC}> + at tests/pass-dep/libc/../../utils/libc.rs:LL:CC + 3: test_getsockname_ipv4_connect_nonblock + at tests/pass-dep/libc/libc-socket-no-blocking.rs:LL:CC + 4: main + at tests/pass-dep/libc/libc-socket-no-blocking.rs:LL:CC + 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 173edbc822a7..a953796837f1 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-socket.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-socket.rs @@ -41,6 +41,7 @@ fn main() { test_getsockname_ipv4(); test_getsockname_ipv4_random_port(); test_getsockname_ipv4_unbound(); + test_getsockname_ipv4_connect(); test_getsockname_ipv6(); test_getpeername_ipv4(); @@ -49,6 +50,8 @@ fn main() { test_shutdown(); test_shutdown_readable_after_write_close(); test_shutdown_writable_after_read_close(); + + test_getsockopt_truncate(); } /// Test creating a socket and then closing it afterwards. @@ -268,12 +271,12 @@ fn test_send_peek_recv() { // Write the bytes into the stream. unsafe { - errno_result(libc_utils::write_all_generic( + libc_utils::write_all_generic( TEST_BYTES.as_ptr().cast(), TEST_BYTES.len(), libc_utils::NoRetry, |buf, count| libc::send(peerfd, buf, count, 0), - )) + ) .unwrap() }; }); @@ -300,12 +303,12 @@ fn test_send_peek_recv() { let mut buffer = [0; TEST_BYTES.len()]; unsafe { - errno_result(libc_utils::read_all_generic( + libc_utils::read_exact_generic( buffer.as_mut_ptr().cast(), buffer.len(), libc_utils::NoRetry, |buf, count| libc::recv(client_sockfd, buf, count, 0), - )) + ) .unwrap() }; assert_eq!(&buffer, TEST_BYTES); @@ -327,24 +330,13 @@ fn test_write_read() { let (peerfd, _) = net::accept_ipv4(server_sockfd).unwrap(); // Write some bytes into the stream. - let bytes_written = unsafe { - errno_result(libc_utils::write_all( - peerfd, - TEST_BYTES.as_ptr().cast(), - TEST_BYTES.len(), - )) - .unwrap() - }; - assert_eq!(bytes_written as usize, TEST_BYTES.len()); + libc_utils::write_all(peerfd, TEST_BYTES).unwrap(); }); net::connect_ipv4(client_sockfd, addr).unwrap(); let mut buffer = [0; TEST_BYTES.len()]; - unsafe { - errno_result(libc_utils::read_all(client_sockfd, buffer.as_mut_ptr().cast(), buffer.len())) - .unwrap() - }; + libc_utils::read_exact(client_sockfd, &mut buffer).unwrap(); assert_eq!(&buffer, TEST_BYTES); server_thread.join().unwrap(); @@ -427,6 +419,34 @@ fn test_getsockname_ipv4_unbound() { assert_eq!(addr.sin_addr.s_addr, sock_addr.sin_addr.s_addr); } +/// Test the `getsockname` syscall on a connected IPv4 socket. +fn test_getsockname_ipv4_connect() { + 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() }; + + // Spawn the server thread. + let server_thread = thread::spawn(move || net::accept_ipv4(server_sockfd).unwrap()); + + net::connect_ipv4(client_sockfd, addr).unwrap(); + + let (_, sock_addr) = net::sockname_ipv4(|storage, len| unsafe { + libc::getsockname(client_sockfd, storage, len) + }) + .unwrap(); + + // We want to ensure that the local address is not the unspecified address. + // Because the bound address could be of any local interface, we cannot + // test for localhost. + let addr = net::sock_addr_ipv4([0, 0, 0, 0], 0); + + 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. /// The `getsockname` syscall should return the same address as to /// which the socket was bound to. @@ -611,3 +631,53 @@ fn test_shutdown_writable_after_read_close() { server_thread.join().unwrap(); } + +/// Test that the value gets silently truncated when a too small +/// length is provided and that the length gets reduced when the value +/// is smaller than the provided length. +fn test_getsockopt_truncate() { + let (sockfd, _) = net::make_listener_ipv4().unwrap(); + + // The actual TTL with a correctly sized buffer. + let ttl = net::getsockopt::(sockfd, libc::IPPROTO_IP, libc::IP_TTL).unwrap(); + + let mut option_value = std::mem::MaybeUninit::::zeroed(); + // The actual length is 4 bytes. + let mut short_option_len = 2 as libc::socklen_t; + + errno_result(unsafe { + libc::getsockopt( + sockfd, + libc::IPPROTO_IP, + libc::IP_TTL, + option_value.as_mut_ptr().cast(), + &mut short_option_len, + ) + }) + .unwrap(); + // Ensure that the size wasn't changed. + assert_eq!(short_option_len, 2); + let short_ttl = unsafe { option_value.assume_init() }; + + // Assert that the value was silently truncated. + assert_eq!(short_ttl.to_ne_bytes()[0..2], ttl.to_ne_bytes()[0..2]); + + let mut option_value = std::mem::MaybeUninit::::zeroed(); + // The actual length is 4 bytes. + let mut long_option_len = 6 as libc::socklen_t; + + errno_result(unsafe { + libc::getsockopt( + sockfd, + libc::IPPROTO_IP, + libc::IP_TTL, + option_value.as_mut_ptr().cast(), + &mut long_option_len, + ) + }) + .unwrap(); + // Ensure that the size was shortened to the actual length. + assert_eq!(long_option_len, 4); + let long_ttl = unsafe { option_value.assume_init() }; + assert_eq!(long_ttl, ttl); +} diff --git a/src/tools/miri/tests/pass-dep/libc/libc-socketpair.rs b/src/tools/miri/tests/pass-dep/libc/libc-socketpair.rs index 20424fc86dc2..da521600d84a 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-socketpair.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-socketpair.rs @@ -26,46 +26,50 @@ fn test_socketpair() { // Read size == data available in buffer. let data = b"abcde"; - write_all_from_slice(fds[0], data).unwrap(); - let buf = read_all_into_array::<5>(fds[1]).unwrap(); + write_all(fds[0], data).unwrap(); + let buf = read_exact_array::<5>(fds[1]).unwrap(); assert_eq!(&buf, data); // Read size > data available in buffer. let data = b"abc"; - write_all_from_slice(fds[0], data).unwrap(); + write_all(fds[0], data).unwrap(); let mut buf2: [u8; 5] = [0; 5]; - let (read, rest) = read_into_slice(fds[1], &mut buf2).unwrap(); + let (read, rest) = read_split_slice(fds[1], &mut buf2).unwrap(); assert_eq!(read[..], data[..read.len()]); // Write 2 more bytes so we can exactly fill the `rest`. - write_all_from_slice(fds[0], b"12").unwrap(); - read_all_into_slice(fds[1], rest).unwrap(); + write_all(fds[0], b"12").unwrap(); + read_exact(fds[1], rest).unwrap(); + assert_eq!(&buf2, b"abc12"); // Test read and write from another direction. // Read size == data available in buffer. let data = b"12345"; - write_all_from_slice(fds[1], data).unwrap(); - let buf3 = read_all_into_array::<5>(fds[0]).unwrap(); + write_all(fds[1], data).unwrap(); + let buf3 = read_exact_array::<5>(fds[0]).unwrap(); assert_eq!(&buf3, data); // Read size > data available in buffer. - let data = b"123"; - write_all_from_slice(fds[1], data).unwrap(); + let data = b"abc"; + write_all(fds[1], data).unwrap(); let mut buf4: [u8; 5] = [0; 5]; - let (read, rest) = read_into_slice(fds[0], &mut buf4).unwrap(); + let (read, rest) = read_split_slice(fds[0], &mut buf4).unwrap(); assert_eq!(read[..], data[..read.len()]); // Write 2 more bytes so we can exactly fill the `rest`. - write_all_from_slice(fds[1], b"12").unwrap(); - read_all_into_slice(fds[0], rest).unwrap(); + write_all(fds[1], b"12").unwrap(); + read_exact(fds[0], rest).unwrap(); + assert_eq!(&buf4, b"abc12"); // Test when happens when we close one end, with some data in the buffer. - write_all_from_slice(fds[0], data).unwrap(); + write_all(fds[0], data).unwrap(); errno_check(unsafe { libc::close(fds[0]) }); // Reading the other end should return that data, then EOF. let mut buf: [u8; 5] = [0; 5]; - let (res, _) = read_until_eof_into_slice(fds[1], &mut buf).unwrap(); - assert_eq!(res, data); + let (read, _tail) = read_split_slice(fds[1], &mut buf).unwrap(); + assert_eq!(read, data); + let (read, _tail) = read_split_slice(fds[1], &mut buf).unwrap(); + assert_eq!(read, &[]); // Writing the other end should emit EPIPE. - let err = write_all_from_slice(fds[1], &mut buf).unwrap_err(); + let err = write_all(fds[1], &mut buf).unwrap_err(); assert_eq!(err.raw_os_error(), Some(libc::EPIPE)); } @@ -74,19 +78,19 @@ fn test_socketpair_threaded() { errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) }); let thread1 = thread::spawn(move || { - let buf = read_all_into_array::<5>(fds[1]).unwrap(); + let buf = read_exact_array::<5>(fds[1]).unwrap(); assert_eq!(&buf, b"abcde"); }); thread::yield_now(); - write_all_from_slice(fds[0], b"abcde").unwrap(); + write_all(fds[0], b"abcde").unwrap(); thread1.join().unwrap(); // Read and write from different direction let thread2 = thread::spawn(move || { thread::yield_now(); - write_all_from_slice(fds[1], b"12345").unwrap(); + write_all(fds[1], b"12345").unwrap(); }); - let buf = read_all_into_array::<5>(fds[0]).unwrap(); + let buf = read_exact_array::<5>(fds[0]).unwrap(); assert_eq!(&buf, b"12345"); thread2.join().unwrap(); } @@ -98,13 +102,13 @@ fn test_race() { let thread1 = thread::spawn(move || { // write() from the main thread will occur before the read() here // because preemption is disabled and the main thread yields after write(). - let buf = read_all_into_array::<1>(fds[1]).unwrap(); + let buf = read_exact_array::<1>(fds[1]).unwrap(); assert_eq!(&buf, b"a"); // The read above establishes a happens-before so it is now safe to access this global variable. unsafe { assert_eq!(VAL, 1) }; }); unsafe { VAL = 1 }; - write_all_from_slice(fds[0], b"a").unwrap(); + write_all(fds[0], b"a").unwrap(); thread::yield_now(); thread1.join().unwrap(); } @@ -115,12 +119,12 @@ fn test_blocking_read() { errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) }); let thread1 = thread::spawn(move || { // Let this thread block on read. - let buf = read_all_into_array::<3>(fds[1]).unwrap(); + let buf = read_exact_array::<3>(fds[1]).unwrap(); assert_eq!(&buf, b"abc"); }); let thread2 = thread::spawn(move || { // Unblock thread1 by doing writing something. - write_all_from_slice(fds[0], b"abc").unwrap(); + write_all(fds[0], b"abc").unwrap(); }); thread1.join().unwrap(); thread2.join().unwrap(); @@ -132,14 +136,14 @@ fn test_blocking_write() { errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) }); let arr1: [u8; 0x34000] = [1; 0x34000]; // Exhaust the space in the buffer so the subsequent write will block. - write_all_from_slice(fds[0], &arr1).unwrap(); + write_all(fds[0], &arr1).unwrap(); let thread1 = thread::spawn(move || { // The write below will be blocked because the buffer is already full. - write_all_from_slice(fds[0], b"abc").unwrap(); + write_all(fds[0], b"abc").unwrap(); }); let thread2 = thread::spawn(move || { // Unblock thread1 by freeing up some space. - let buf = read_all_into_array::<3>(fds[1]).unwrap(); + let buf = read_exact_array::<3>(fds[1]).unwrap(); assert_eq!(buf, [1, 1, 1]); }); thread1.join().unwrap(); diff --git a/src/tools/miri/tests/pass-dep/libc/libc-strerror_r.rs b/src/tools/miri/tests/pass-dep/libc/libc-strerror_r.rs index 09885ce839d0..d356a1c64245 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-strerror_r.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-strerror_r.rs @@ -2,7 +2,7 @@ fn main() { unsafe { - let mut buf = vec![0u8; 32]; + let mut buf = vec![0u8; 64]; assert_eq!(libc::strerror_r(libc::EPERM, buf.as_mut_ptr().cast(), buf.len()), 0); let mut buf2 = vec![0u8; 64]; assert_eq!(libc::strerror_r(-1i32, buf2.as_mut_ptr().cast(), buf2.len()), 0); diff --git a/src/tools/miri/tests/pass-dep/tokio/socket.rs b/src/tools/miri/tests/pass-dep/tokio/socket.rs new file mode 100644 index 000000000000..a8014fa8ce7b --- /dev/null +++ b/src/tools/miri/tests/pass-dep/tokio/socket.rs @@ -0,0 +1,56 @@ +//@only-target: linux # We only support tokio on Linux +//@compile-flags: -Zmiri-disable-isolation + +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::net::{TcpListener, TcpStream}; + +const TEST_BYTES: &[u8] = b"these are some test bytes!"; + +#[tokio::main] +async fn main() { + test_accept_and_connect().await; + test_read_write().await; +} + +/// Test connecting and accepting a connection. +async fn test_accept_and_connect() { + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + // Get local address with randomized port to know where + // we need to connect to. + let address = listener.local_addr().unwrap(); + + // Start server thread. + tokio::spawn(async move { + let (_stream, _addr) = listener.accept().await.unwrap(); + }); + + let _stream = TcpStream::connect(address).await.unwrap(); +} + +/// Test writing bytes into and reading bytes from a connected stream. +async fn test_read_write() { + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + // Get local address with randomized port to know where + // we need to connect to. + let address = listener.local_addr().unwrap(); + + // Start server thread. + tokio::spawn(async move { + let (mut stream, _addr) = listener.accept().await.unwrap(); + + stream.write_all(TEST_BYTES).await.unwrap(); + + let mut buffer = [0; TEST_BYTES.len()]; + stream.read_exact(&mut buffer).await.unwrap(); + + assert_eq!(&buffer, TEST_BYTES); + }); + + let mut stream = TcpStream::connect(address).await.unwrap(); + + let mut buffer = [0; TEST_BYTES.len()]; + stream.read_exact(&mut buffer).await.unwrap(); + assert_eq!(&buffer, TEST_BYTES); + + stream.write_all(TEST_BYTES).await.unwrap(); +} diff --git a/src/tools/miri/tests/pass/alloc-access-tracking.stderr b/src/tools/miri/tests/pass/alloc-access-tracking.stderr index 0e25be460cbb..bcc1d987330a 100644 --- a/src/tools/miri/tests/pass/alloc-access-tracking.stderr +++ b/src/tools/miri/tests/pass/alloc-access-tracking.stderr @@ -25,7 +25,7 @@ LL | self.1.deallocate(From::from(ptr.cast()), layout); = note: stack backtrace: 0: > as std::ops::Drop>::drop at RUSTLIB/alloc/src/boxed.rs:LL:CC - 1: std::ptr::drop_glue)) + 1: std::ptr::drop_glue::>> - shim(Some(std::boxed::Box>)) at RUSTLIB/core/src/ptr/mod.rs:LL:CC 2: main at tests/pass/alloc-access-tracking.rs:LL:CC diff --git a/src/tools/miri/tests/pass/backtrace/backtrace-global-alloc.stderr b/src/tools/miri/tests/pass/backtrace/backtrace-global-alloc.stderr index b4ef3f76c20f..bf443274a958 100644 --- a/src/tools/miri/tests/pass/backtrace/backtrace-global-alloc.stderr +++ b/src/tools/miri/tests/pass/backtrace/backtrace-global-alloc.stderr @@ -2,27 +2,27 @@ at tests/pass/backtrace/backtrace-global-alloc.rs:LL:CC 1: >::call_once - shim(fn()) at RUSTLIB/core/src/ops/function.rs:LL:CC - 2: std::sys::backtrace::__rust_begin_short_backtrace + 2: std::sys::backtrace::__rust_begin_short_backtrace:: at RUSTLIB/std/src/sys/backtrace.rs:LL:CC - 3: std::rt::lang_start::{closure#0} + 3: std::rt::lang_start::<()>::{closure#0} at RUSTLIB/std/src/rt.rs:LL:CC - 4: std::ops::function::impls::call_once + 4: std::ops::function::impls:: for &dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe>::call_once at RUSTLIB/core/src/ops/function.rs:LL:CC - 5: std::panicking::catch_unwind::do_call + 5: std::panicking::catch_unwind::do_call::<&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe, i32> at RUSTLIB/std/src/panicking.rs:LL:CC - 6: std::panicking::catch_unwind + 6: std::panicking::catch_unwind:: i32 + std::marker::Sync + std::panic::RefUnwindSafe> at RUSTLIB/std/src/panicking.rs:LL:CC - 7: std::panic::catch_unwind + 7: std::panic::catch_unwind::<&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe, i32> at RUSTLIB/std/src/panic.rs:LL:CC 8: std::rt::lang_start_internal::{closure#0} at RUSTLIB/std/src/rt.rs:LL:CC - 9: std::panicking::catch_unwind::do_call + 9: std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize> at RUSTLIB/std/src/panicking.rs:LL:CC - 10: std::panicking::catch_unwind + 10: std::panicking::catch_unwind:: at RUSTLIB/std/src/panicking.rs:LL:CC - 11: std::panic::catch_unwind + 11: std::panic::catch_unwind::<{closure@std::rt::lang_start_internal::{closure#0}}, isize> at RUSTLIB/std/src/panic.rs:LL:CC 12: std::rt::lang_start_internal at RUSTLIB/std/src/rt.rs:LL:CC - 13: std::rt::lang_start + 13: std::rt::lang_start::<()> at RUSTLIB/std/src/rt.rs:LL:CC diff --git a/src/tools/miri/tests/pass/backtrace/backtrace-std.stderr b/src/tools/miri/tests/pass/backtrace/backtrace-std.stderr index 706eacc70fd8..ee0c65fa6eb6 100644 --- a/src/tools/miri/tests/pass/backtrace/backtrace-std.stderr +++ b/src/tools/miri/tests/pass/backtrace/backtrace-std.stderr @@ -2,7 +2,7 @@ at tests/pass/backtrace/backtrace-std.rs:LL:CC 1: func_c at tests/pass/backtrace/backtrace-std.rs:LL:CC - 2: func_b + 2: func_b:: at tests/pass/backtrace/backtrace-std.rs:LL:CC 3: func_a at tests/pass/backtrace/backtrace-std.rs:LL:CC @@ -10,27 +10,27 @@ at tests/pass/backtrace/backtrace-std.rs:LL:CC 5: >::call_once - shim(fn()) at RUSTLIB/core/src/ops/function.rs:LL:CC - 6: std::sys::backtrace::__rust_begin_short_backtrace + 6: std::sys::backtrace::__rust_begin_short_backtrace:: at RUSTLIB/std/src/sys/backtrace.rs:LL:CC - 7: std::rt::lang_start::{closure#0} + 7: std::rt::lang_start::<()>::{closure#0} at RUSTLIB/std/src/rt.rs:LL:CC - 8: std::ops::function::impls::call_once + 8: std::ops::function::impls:: for &dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe>::call_once at RUSTLIB/core/src/ops/function.rs:LL:CC - 9: std::panicking::catch_unwind::do_call + 9: std::panicking::catch_unwind::do_call::<&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe, i32> at RUSTLIB/std/src/panicking.rs:LL:CC - 10: std::panicking::catch_unwind + 10: std::panicking::catch_unwind:: i32 + std::marker::Sync + std::panic::RefUnwindSafe> at RUSTLIB/std/src/panicking.rs:LL:CC - 11: std::panic::catch_unwind + 11: std::panic::catch_unwind::<&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe, i32> at RUSTLIB/std/src/panic.rs:LL:CC 12: std::rt::lang_start_internal::{closure#0} at RUSTLIB/std/src/rt.rs:LL:CC - 13: std::panicking::catch_unwind::do_call + 13: std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize> at RUSTLIB/std/src/panicking.rs:LL:CC - 14: std::panicking::catch_unwind + 14: std::panicking::catch_unwind:: at RUSTLIB/std/src/panicking.rs:LL:CC - 15: std::panic::catch_unwind + 15: std::panic::catch_unwind::<{closure@std::rt::lang_start_internal::{closure#0}}, isize> at RUSTLIB/std/src/panic.rs:LL:CC 16: std::rt::lang_start_internal at RUSTLIB/std/src/rt.rs:LL:CC - 17: std::rt::lang_start + 17: std::rt::lang_start::<()> at RUSTLIB/std/src/rt.rs:LL:CC diff --git a/src/tools/miri/tests/pass/function_calls/abi_compat.rs b/src/tools/miri/tests/pass/function_calls/abi_compat.rs index ca76897faea8..37795daa55c8 100644 --- a/src/tools/miri/tests/pass/function_calls/abi_compat.rs +++ b/src/tools/miri/tests/pass/function_calls/abi_compat.rs @@ -66,7 +66,7 @@ fn test_abi_newtype() { #[repr(transparent)] #[derive(Copy, Clone)] enum Wrapper4 { - V(Zst, T, [u8; 0]) + V(Zst, T, [u8; 0]), } let t = T::default(); diff --git a/src/tools/miri/tests/pass/open_a_file_in_proc.stderr b/src/tools/miri/tests/pass/open_a_file_in_proc.stderr index 0ba31ed5adac..c80b11ecb37b 100644 --- a/src/tools/miri/tests/pass/open_a_file_in_proc.stderr +++ b/src/tools/miri/tests/pass/open_a_file_in_proc.stderr @@ -7,25 +7,25 @@ LL | let fd = cvt_r(|| unsafe { open64(path.as_ptr(), flags, opts.mode a = note: stack backtrace: 0: std::sys::fs::PLATFORM::File::open_c::{closure#0} at RUSTLIB/std/src/sys/fs/PLATFORM.rs:LL:CC - 1: std::sys::pal::PLATFORM::cvt_r + 1: std::sys::pal::PLATFORM::cvt_r:: at RUSTLIB/std/src/sys/pal/PLATFORM/mod.rs:LL:CC 2: std::sys::fs::PLATFORM::File::open_c at RUSTLIB/std/src/sys/fs/PLATFORM.rs:LL:CC 3: std::sys::fs::PLATFORM::File::open::{closure#0} at RUSTLIB/std/src/sys/fs/PLATFORM.rs:LL:CC - 4: std::sys::helpers::small_c_string::run_with_cstr_stack + 4: std::sys::helpers::small_c_string::run_with_cstr_stack:: at RUSTLIB/std/src/sys/helpers/small_c_string.rs:LL:CC - 5: std::sys::helpers::small_c_string::run_with_cstr + 5: std::sys::helpers::small_c_string::run_with_cstr:: at RUSTLIB/std/src/sys/helpers/small_c_string.rs:LL:CC - 6: std::sys::helpers::small_c_string::run_path_with_cstr + 6: std::sys::helpers::small_c_string::run_path_with_cstr:: at RUSTLIB/std/src/sys/helpers/small_c_string.rs:LL:CC 7: std::sys::fs::PLATFORM::File::open at RUSTLIB/std/src/sys/fs/PLATFORM.rs:LL:CC 8: std::fs::OpenOptions::_open at RUSTLIB/std/src/fs.rs:LL:CC - 9: std::fs::OpenOptions::open + 9: std::fs::OpenOptions::open::<&std::path::Path> at RUSTLIB/std/src/fs.rs:LL:CC - 10: std::fs::File::open + 10: std::fs::File::open::<&str> at RUSTLIB/std/src/fs.rs:LL:CC 11: main at tests/pass/open_a_file_in_proc.rs:LL:CC diff --git a/src/tools/miri/tests/pass/shims/fs-permissions.rs b/src/tools/miri/tests/pass/shims/fs-permissions.rs new file mode 100644 index 000000000000..51b371e83ba5 --- /dev/null +++ b/src/tools/miri/tests/pass/shims/fs-permissions.rs @@ -0,0 +1,61 @@ +//@compile-flags: -Zmiri-disable-isolation +//@ignore-target: windows # shim not supported +//@ignore-host: windows # needs unix PermissionExt + +use std::fs::{self, File}; + +#[path = "../../utils/mod.rs"] +mod utils; + +macro_rules! check { + ($e:expr) => { + match $e { + Ok(t) => t, + Err(e) => panic!("{} failed with: {e}", stringify!($e)), + } + }; +} + +fn main() { + chmod_works(); + fchmod_works(); +} + +fn chmod_works() { + let tmpdir = utils::tmp(); + let file = tmpdir.join("miri_test_fs_set_permissions.txt"); + + check!(File::create(&file)); + let attr = check!(fs::metadata(&file)); + assert!(!attr.permissions().readonly()); + let mut p = attr.permissions(); + p.set_readonly(true); + check!(fs::set_permissions(&file, p.clone())); + let attr = check!(fs::metadata(&file)); + assert!(attr.permissions().readonly()); + + match fs::set_permissions(&tmpdir.join("foo"), p.clone()) { + Ok(..) => panic!("wanted an error"), + Err(..) => {} + } + + p.set_readonly(false); + check!(fs::set_permissions(&file, p)); +} + +fn fchmod_works() { + let tmpdir = utils::tmp(); + let path = tmpdir.join("miri_test_file_set_permissions.txt"); + + let file = check!(File::create(&path)); + let attr = check!(fs::metadata(&path)); + assert!(!attr.permissions().readonly()); + let mut p = attr.permissions(); + p.set_readonly(true); + check!(file.set_permissions(p.clone())); + let attr = check!(fs::metadata(&path)); + assert!(attr.permissions().readonly()); + + p.set_readonly(false); + check!(file.set_permissions(p)); +} diff --git a/src/tools/miri/tests/pass/shims/fs.rs b/src/tools/miri/tests/pass/shims/fs.rs index e6c15c81d9fd..e0da9f63876f 100644 --- a/src/tools/miri/tests/pass/shims/fs.rs +++ b/src/tools/miri/tests/pass/shims/fs.rs @@ -54,6 +54,7 @@ fn test_file() { // Test creating, writing and closing a file (closing is tested when `file` is dropped). let mut file = File::create(&path).unwrap(); + assert!(!file.metadata().unwrap().permissions().readonly()); // new file shouldn't be read-only // Writing 0 bytes should not change the file contents. file.write(&mut []).unwrap(); assert_eq!(file.metadata().unwrap().len(), 0); diff --git a/src/tools/miri/tests/pass/shims/socket-address.rs b/src/tools/miri/tests/pass/shims/socket-address.rs new file mode 100644 index 000000000000..144584b47d91 --- /dev/null +++ b/src/tools/miri/tests/pass/shims/socket-address.rs @@ -0,0 +1,38 @@ +//@ignore-target: windows # No socket address support on Windows +//@compile-flags: -Zmiri-disable-isolation +//@normalize-stderr-test: "address resolution failed: .*" -> "address resolution failed: $$MSG" + +use std::net::{ + IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, TcpListener, ToSocketAddrs, +}; + +fn main() { + test_address_resolution(); +} + +/// Test getting a socket address from a hostname and a port. +fn test_address_resolution() { + let listener = TcpListener::bind("localhost:0").unwrap(); + let address = listener.local_addr().unwrap(); + match address.ip() { + IpAddr::V4(addr) => assert_eq!(addr, Ipv4Addr::LOCALHOST), + IpAddr::V6(addr) => assert_eq!(addr, Ipv6Addr::LOCALHOST), + } + + let addr_str = "localhost:8888"; + let mut addr_count = 0; + for addr in addr_str.to_socket_addrs().unwrap() { + addr_count += 1; + match addr { + SocketAddr::V4(addr) => assert_eq!(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 8888), addr), + SocketAddr::V6(addr) => + assert_eq!(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 8888, 0, 0), addr), + } + } + // We expect an IPv4 and an IPv6 address. + assert!(addr_count == 2); + + // Resolving an invalid name should error. Needs the port to even hit `getaddrinfo`. + let addr_str = "this-is-not-a-valid-address:80"; + addr_str.to_socket_addrs().unwrap_err(); +} diff --git a/src/tools/miri/tests/pass/shims/socket-address.stderr b/src/tools/miri/tests/pass/shims/socket-address.stderr new file mode 100644 index 000000000000..7091c3b6c6d5 --- /dev/null +++ b/src/tools/miri/tests/pass/shims/socket-address.stderr @@ -0,0 +1,25 @@ +warning: address resolution failed: $MSG + --> RUSTLIB/std/src/sys/net/PLATFORM/socket/mod.rs:LL:CC + | +LL | cvt_gai(c::getaddrinfo(c_host.as_ptr(), ptr::null(), &hints, &mut res)) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error during address resolution + | + = note: Miri cannot return proper error information from this call; only a generic error code is being returned + = note: stack backtrace: + 0: std::sys::net::PLATFORM::socket::lookup_host::{closure#0} + at RUSTLIB/std/src/sys/net/PLATFORM/socket/mod.rs:LL:CC + 1: std::sys::helpers::small_c_string::run_with_cstr_stack:: + at RUSTLIB/std/src/sys/helpers/small_c_string.rs:LL:CC + 2: std::sys::helpers::small_c_string::run_with_cstr:: + at RUSTLIB/std/src/sys/helpers/small_c_string.rs:LL:CC + 3: std::sys::net::PLATFORM::socket::lookup_host + at RUSTLIB/std/src/sys/net/PLATFORM/socket/mod.rs:LL:CC + 4: std::sys::net::PLATFORM::lookup_host_string + at RUSTLIB/std/src/sys/net/PLATFORM/mod.rs:LL:CC + 5: ::to_socket_addrs + at RUSTLIB/std/src/net/socket_addr.rs:LL:CC + 6: test_address_resolution + at tests/pass/shims/socket-address.rs:LL:CC + 7: main + at tests/pass/shims/socket-address.rs:LL:CC + diff --git a/src/tools/miri/tests/pass/shims/socket.rs b/src/tools/miri/tests/pass/shims/socket.rs index 584ce8f60ce9..4f448fa44780 100644 --- a/src/tools/miri/tests/pass/shims/socket.rs +++ b/src/tools/miri/tests/pass/shims/socket.rs @@ -1,4 +1,4 @@ -//@ignore-target: windows # No libc socket on Windows +//@ignore-target: windows # No socket support on Windows //@compile-flags: -Zmiri-disable-isolation use std::io::{ErrorKind, Read, Write}; @@ -15,6 +15,7 @@ fn main() { test_peek(); test_peer_addr(); test_shutdown(); + test_sockopt_ttl(); } fn test_create_ipv4_listener() { @@ -152,3 +153,11 @@ fn test_shutdown() { let _stream = handle.join().unwrap(); } + +/// 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(); + + // TODO: Once we support setting the TTL we should also test it here. +} diff --git a/src/tools/miri/tests/pass/shims/time-with-isolation.rs b/src/tools/miri/tests/pass/shims/time-with-isolation.rs index e7b162441235..524745c36299 100644 --- a/src/tools/miri/tests/pass/shims/time-with-isolation.rs +++ b/src/tools/miri/tests/pass/shims/time-with-isolation.rs @@ -1,5 +1,13 @@ +#![feature(duration_constructors, thread_sleep_until)] use std::time::{Duration, Instant}; +fn test_underflow() { + // The time 1 day before the program started should be representable. + // (This used to underflow on Windows.) + let now = Instant::now(); + let _earlier = now - Duration::from_days(1); +} + fn test_sleep() { // We sleep a *long* time here -- but the clock is virtual so the test should still pass quickly. let before = Instant::now(); @@ -8,6 +16,14 @@ fn test_sleep() { assert!((after - before).as_secs() >= 3600); } +fn test_sleep_until() { + let before = Instant::now(); + let hunderd_millis_after_start = before + Duration::from_millis(100); + std::thread::sleep_until(hunderd_millis_after_start); + let after = Instant::now(); + assert!((after - before).as_millis() >= 100); +} + /// Ensure that time passes even if we don't sleep (but just work). fn test_time_passes() { // Check `Instant`. @@ -48,8 +64,10 @@ fn test_deterministic() { } fn main() { + test_underflow(); test_time_passes(); test_block_for_one_second(); test_sleep(); + test_sleep_until(); test_deterministic(); } diff --git a/src/tools/miri/tests/pass/shims/time.rs b/src/tools/miri/tests/pass/shims/time.rs index ef0b400f1a71..627ba3af8a34 100644 --- a/src/tools/miri/tests/pass/shims/time.rs +++ b/src/tools/miri/tests/pass/shims/time.rs @@ -1,5 +1,5 @@ //@compile-flags: -Zmiri-disable-isolation -#![feature(thread_sleep_until)] +#![feature(duration_constructors, thread_sleep_until)] use std::time::{Duration, Instant, SystemTime}; @@ -9,6 +9,13 @@ fn duration_sanity(diff: Duration) { assert!(diff.as_millis() < 1000); // macOS is very slow sometimes } +fn test_underflow() { + // The time 1 day before the program started should be representable. + // (This used to underflow on Windows.) + let now = Instant::now(); + let _earlier = now - Duration::from_days(1); +} + fn test_sleep() { let before = Instant::now(); std::thread::sleep(Duration::from_millis(100)); @@ -57,6 +64,7 @@ fn main() { assert_eq!(now2 - diff, now1); duration_sanity(diff); + test_underflow(); test_sleep(); test_sleep_until(); } diff --git a/src/tools/miri/tests/ui.rs b/src/tools/miri/tests/ui.rs index 10064bc2bbb3..23125d116cf1 100644 --- a/src/tools/miri/tests/ui.rs +++ b/src/tools/miri/tests/ui.rs @@ -267,8 +267,6 @@ fn $name() -> &'static [(Match, &'static [u8])] { "<[0-9]+=" => " "$1", - // erase generics in backtraces - "([0-9]+: .*)::<.*>" => "$1", // erase long hexadecimals r"0x[0-9a-fA-F]+[0-9a-fA-F]{2,2}" => "$$HEX", // erase specific alignments diff --git a/src/tools/miri/tests/utils/libc.rs b/src/tools/miri/tests/utils/libc.rs index 26797ee4c3cb..da331a3c934e 100644 --- a/src/tools/miri/tests/utils/libc.rs +++ b/src/tools/miri/tests/utils/libc.rs @@ -32,12 +32,14 @@ pub fn errno_check + Ord + fmt::Debug>(ret: T) { } /// Invoke the `read` function until `buf` is full. `retry` contols the behavior on EAGAIN. -pub unsafe fn read_all_generic( +/// Panics if we get EOF before the buffer is filled. +#[track_caller] +pub unsafe fn read_exact_generic( buf: *mut libc::c_void, count: libc::size_t, retry: Retry, read: impl Fn(*mut libc::c_void, libc::size_t) -> libc::ssize_t, -) -> libc::ssize_t { +) -> io::Result<()> { assert!(count > 0); let mut read_so_far = 0; while read_so_far < count { @@ -50,68 +52,52 @@ pub unsafe fn read_all_generic( continue; } } - return res; + return Err(io::Error::last_os_error()); } if res == 0 { - // EOF - break; + // We expected more data but got EOF. + panic!( + "could not fill buffer with {count} bytes: EOF received after {read_so_far} bytes" + ); } read_so_far += res as libc::size_t; } - return read_so_far as libc::ssize_t; -} - -/// Read from `fd` until `buf` is full. Abort on first error. -pub unsafe fn read_all( - fd: libc::c_int, - buf: *mut libc::c_void, - count: libc::size_t, -) -> libc::ssize_t { - read_all_generic(buf, count, NoRetry, |buf, count| libc::read(fd, buf, count)) + Ok(()) } /// Try to fill the given slice by reading from `fd`. Panic if that many bytes could not be read. #[track_caller] -pub fn read_all_into_slice(fd: libc::c_int, buf: &mut [u8]) -> io::Result<()> { - let res = errno_result(unsafe { read_all(fd, buf.as_mut_ptr().cast(), buf.len()) })?; - assert_eq!(res as usize, buf.len()); - Ok(()) +pub fn read_exact(fd: libc::c_int, buf: &mut [u8]) -> io::Result<()> { + unsafe { + read_exact_generic(buf.as_mut_ptr().cast(), buf.len(), NoRetry, |buf, count| { + libc::read(fd, buf, count) + }) + } } /// Read exactly `N` bytes from `fd`. Error if that many bytes could not be read. #[track_caller] -pub fn read_all_into_array(fd: libc::c_int) -> io::Result<[u8; N]> { +pub fn read_exact_array(fd: libc::c_int) -> io::Result<[u8; N]> { let mut buf = [0; N]; - read_all_into_slice(fd, &mut buf)?; + read_exact(fd, &mut buf)?; Ok(buf) } /// Do a single read from `fd` and return the part of the buffer that was written into, /// and the rest. #[track_caller] -pub fn read_into_slice(fd: libc::c_int, buf: &mut [u8]) -> io::Result<(&mut [u8], &mut [u8])> { +pub fn read_split_slice(fd: libc::c_int, buf: &mut [u8]) -> io::Result<(&mut [u8], &mut [u8])> { let res = errno_result(unsafe { libc::read(fd, buf.as_mut_ptr().cast(), buf.len()) })?; Ok(buf.split_at_mut(res as usize)) } -/// Read from `fd` until we get EOF and return the part of the buffer that was written into, -/// and the rest. -#[track_caller] -pub fn read_until_eof_into_slice( - fd: libc::c_int, - buf: &mut [u8], -) -> io::Result<(&mut [u8], &mut [u8])> { - let res = errno_result(unsafe { read_all(fd, buf.as_mut_ptr().cast(), buf.len()) })?; - Ok(buf.split_at_mut(res as usize)) -} - /// Invoke the `write` function until `buf` is full. `retry` controls the behavior on EAGAIN. pub unsafe fn write_all_generic( buf: *const libc::c_void, count: libc::size_t, retry: Retry, write: impl Fn(*const libc::c_void, libc::size_t) -> libc::ssize_t, -) -> libc::ssize_t { +) -> io::Result<()> { assert!(count > 0); let mut written_so_far = 0; while written_so_far < count { @@ -124,29 +110,22 @@ pub unsafe fn write_all_generic( continue; } } - return res; + return Err(io::Error::last_os_error()); } // Apparently a return value of 0 is just a short write, nothing special (unlike reads). written_so_far += res as libc::size_t; } - return written_so_far as libc::ssize_t; -} - -/// Write to `fd` until `buf` is fully written. Abort on first error. -pub unsafe fn write_all( - fd: libc::c_int, - buf: *const libc::c_void, - count: libc::size_t, -) -> libc::ssize_t { - write_all_generic(buf, count, NoRetry, |buf, count| libc::write(fd, buf, count)) + return Ok(()); } /// Write the entire `buf` to `fd`. Panic if not all bytes could be written. #[track_caller] -pub fn write_all_from_slice(fd: libc::c_int, buf: &[u8]) -> io::Result<()> { - let res = errno_result(unsafe { write_all(fd, buf.as_ptr().cast(), buf.len()) })?; - assert_eq!(res as usize, buf.len()); - Ok(()) +pub fn write_all(fd: libc::c_int, buf: &[u8]) -> io::Result<()> { + unsafe { + write_all_generic(buf.as_ptr().cast(), buf.len(), NoRetry, |buf, count| { + libc::write(fd, buf, count) + }) + } } #[cfg(any(target_os = "linux", target_os = "android", target_os = "illumos"))] @@ -155,7 +134,7 @@ pub mod epoll { use libc::c_int; pub use libc::{EPOLL_CTL_ADD, EPOLL_CTL_DEL, EPOLL_CTL_MOD}; // Re-export some constants we need a lot for this. - pub use libc::{EPOLLET, EPOLLHUP, EPOLLIN, EPOLLOUT, EPOLLRDHUP}; + pub use libc::{EPOLLERR, EPOLLET, EPOLLHUP, EPOLLIN, EPOLLOUT, EPOLLRDHUP}; use super::*; @@ -204,6 +183,30 @@ pub fn check_epoll_wait(epfd: i32, expected: &[Ev], timeout: i32 pub fn check_epoll_wait_noblock(epfd: i32, expected: &[Ev]) { check_epoll_wait::(epfd, expected, 0); } + + /// Query the current epoll readiness of a file descriptor. + /// This is done by creating a new epoll instance, adding the + /// fd to the epoll interests and then performing a zero-timeout + /// wait. + pub fn current_epoll_readiness(fd: i32, interests: i32) -> c_int { + let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); + // Add fd with all possible interests to epoll instance. + epoll_ctl_add(epfd, fd, interests).unwrap(); + + let mut array: [libc::epoll_event; N] = [libc::epoll_event { events: 0, u64: 0 }; N]; + let num = errno_result(unsafe { + // Use zero-timeout to just query without waiting. + libc::epoll_wait(epfd, array.as_mut_ptr(), N.try_into().unwrap(), 0) + }) + .expect("epoll_wait returned an error"); + + let mut readiness = 0; + let events = &mut array[..num.try_into().unwrap()]; + events.iter().for_each(|e| { + readiness |= e.events.cast_signed(); + }); + readiness + } } pub mod net { @@ -364,6 +367,35 @@ pub fn setsockopt( Ok(()) } + /// Get a socket option. It's the caller's responsibility that `T` is + /// associated with the given socket option. + /// + /// This function is directly copied from the standard library implementation + /// for sockets on UNIX targets. + pub fn getsockopt( + sockfd: libc::c_int, + level: libc::c_int, + option_name: libc::c_int, + ) -> io::Result { + let mut option_value = std::mem::MaybeUninit::::zeroed(); + let mut option_len = size_of::() as libc::socklen_t; + let provided_len = option_len; + + errno_result(unsafe { + libc::getsockopt( + sockfd, + level, + option_name, + option_value.as_mut_ptr().cast(), + &mut option_len, + ) + })?; + // Ensure that there was no truncation. + assert!(option_len == provided_len); + + Ok(unsafe { option_value.assume_init() }) + } + /// Wraps a call to a platform function that returns an IPv4 socket address. /// Returns a tuple containing the actual return value of the performed /// syscall and the written address of it.