mirror of
https://github.com/rust-lang/rust.git
synced 2026-05-16 13:05:18 +03:00
Rollup merge of #156394 - RalfJung:miri, r=RalfJung
miri subtree update Subtree update of `miri` to https://github.com/rust-lang/miri/commit/f3f1ed1b34320de40d6dfad8f937341be227876d. Created using https://github.com/rust-lang/josh-sync. r? @ghost
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
[package]
|
||||
name = "mse"
|
||||
version = "0.1.0"
|
||||
authors = ["Ralf Jung <post@ralfj.de>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
[package]
|
||||
name = "cargo-miri-test"
|
||||
version = "0.1.0"
|
||||
authors = ["Oliver Schneider <git-spam-no-reply9815368754983@oli-obk.de>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
[package]
|
||||
name = "cargo-miri-test"
|
||||
version = "0.1.0"
|
||||
authors = ["Oliver Schneider <git-spam-no-reply9815368754983@oli-obk.de>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
[package]
|
||||
authors = ["Miri Team"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
name = "genmc-sys"
|
||||
version = "0.1.0"
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
[package]
|
||||
authors = ["Miri Team"]
|
||||
description = "Helpers for miri maintenance"
|
||||
license = "MIT OR Apache-2.0"
|
||||
name = "miri-script"
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
44860d3e9ef700cac0b4a61d924f41f46bf1b447
|
||||
63b1dfc0e00fd6f8ad7cd8817fc712e7d9b7be59
|
||||
|
||||
@@ -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<dyn SourceFileDescription>,
|
||||
/// The threads which are blocked on the I/O source, and the interest indicating
|
||||
/// when they should be unblocked.
|
||||
blocked_threads: BTreeMap<ThreadId, BlockingIoInterest>,
|
||||
}
|
||||
|
||||
/// 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<FdId, (FileDescriptionRef<dyn WithSource>, FxHashMap<InterestReceiver, Interest>)>,
|
||||
/// Map from source file description ids to the actual sources and their
|
||||
/// blocked threads.
|
||||
sources: BTreeMap<FdId, BlockingIoSource>,
|
||||
}
|
||||
|
||||
impl BlockingIoManager {
|
||||
@@ -70,133 +161,176 @@ pub fn new(communicate: bool) -> Result<Self, io::Error> {
|
||||
/// 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<Duration>,
|
||||
) -> Result<Vec<(InterestReceiver, FileDescriptionRef<dyn WithSource>)>, 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::<Vec<_>>();
|
||||
|
||||
// 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::<Vec<_>>();
|
||||
|
||||
// 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<dyn WithSource>,
|
||||
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<dyn SourceFileDescription>) {
|
||||
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<InterestReceiver, Interest>) -> Option<Interest> {
|
||||
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<dyn WithSource>,
|
||||
interests: Interest,
|
||||
source_fd: FileDescriptionRef<dyn SourceFileDescription>,
|
||||
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(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<Epoll> },
|
||||
/// 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<Duration>) -> 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.
|
||||
|
||||
@@ -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![],
|
||||
};
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 _,
|
||||
|
||||
@@ -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<MiriMemoryKind> 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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,14 @@ pub fn id(&self) -> FdId {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> PartialEq for FileDescriptionRef<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0.id == other.0.id
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Eq for FileDescriptionRef<T> {}
|
||||
|
||||
/// Holds a weak reference to the actual file description.
|
||||
#[derive(Debug)]
|
||||
pub struct WeakFileDescriptionRef<T: ?Sized>(Weak<FdIdWith<T>>);
|
||||
|
||||
@@ -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
|
||||
// <https://github.com/rust-lang/rust/blob/HEAD/library/std/src/sys/io/error/windows.rs>.
|
||||
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<std::io::ErrorKind>> {
|
||||
target_errnum: Scalar,
|
||||
) -> InterpResult<'tcx, Option<io::Error>> {
|
||||
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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)?,
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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" => {
|
||||
|
||||
@@ -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::<FileHandle>() 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<FileMetadata, IoError>> {
|
||||
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,
|
||||
}))
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
|
||||
@@ -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<BTreeMap<EpollEventKey, EpollEventInterest>>,
|
||||
@@ -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<Epoll>) -> 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<Epoll>,
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 _;
|
||||
|
||||
@@ -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<SocketState>,
|
||||
/// Whether this fd is non-blocking or not.
|
||||
is_non_block: Cell<bool>,
|
||||
/// The current blocking I/O readiness of the file description.
|
||||
io_readiness: RefCell<BlockingIoSourceReadiness>,
|
||||
/// [`Some`] when the socket had an async error which has not yet been fetched via `SO_ERROR`.
|
||||
error: RefCell<Option<io::Error>>,
|
||||
}
|
||||
|
||||
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 <https://man7.org/linux/man-pages/man7/epoll.7.html> 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 <https://pubs.opengroup.org/onlinepubs/9799919799/functions/getsockopt.html>.
|
||||
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::<Socket>() 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:
|
||||
// <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-getsockname#remarks>
|
||||
// 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<SocketAddr, IoError>> {
|
||||
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<usize, IoError>>,
|
||||
) {
|
||||
) -> 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<Socket>,
|
||||
@@ -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:
|
||||
// <https://github.com/tokio-rs/tokio/blob/6c03e03898d71eca976ee1ad8481cf112ae722ba/tokio/src/io/poll_evented.rs#L240>
|
||||
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:
|
||||
// <https://rust-lang.zulipchat.com/#narrow/channel/219381-t-libs/topic/WSAESHUTDOWN.20error.20on.20Windows/near/591883531>
|
||||
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<usize, IoError>>,
|
||||
) {
|
||||
) -> 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<Socket>,
|
||||
@@ -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:
|
||||
// <https://github.com/tokio-rs/tokio/blob/6c03e03898d71eca976ee1ad8481cf112ae722ba/tokio/src/io/poll_evented.rs#L182>
|
||||
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<Socket> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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::<u16>().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<SocketAddr, IoError>> {
|
||||
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<Item = SocketAddr>,
|
||||
) -> 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(())
|
||||
}
|
||||
}
|
||||
@@ -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" => {
|
||||
|
||||
@@ -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 <https://man7.org/linux/man-pages/man7/epoll.7.html> 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
[package]
|
||||
name = "exported_symbol_dep"
|
||||
version = "0.1.0"
|
||||
authors = ["Miri Team"]
|
||||
edition = "2018"
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
[package]
|
||||
name = "exported_symbol"
|
||||
version = "0.1.0"
|
||||
authors = ["Miri Team"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
[package]
|
||||
name = "issue_1567"
|
||||
version = "0.1.0"
|
||||
authors = ["Miri Team"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
[package]
|
||||
name = "issue_1691"
|
||||
version = "0.1.0"
|
||||
authors = ["Miri Team"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
[package]
|
||||
name = "issue_1705"
|
||||
version = "0.1.0"
|
||||
authors = ["Miri Team"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
[package]
|
||||
name = "issue_rust_86261"
|
||||
version = "0.1.0"
|
||||
authors = ["Miri Team"]
|
||||
edition = "2018"
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
# regression test for issue 1760
|
||||
name = "proc_macro_crate"
|
||||
version = "0.1.0"
|
||||
authors = ["Miri Team"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
+2
-2
@@ -10,9 +10,9 @@ LL | self.1.deallocate(From::from(ptr.cast()), layout);
|
||||
= note: stack backtrace:
|
||||
0: <std::boxed::Box<Mutex> as std::ops::Drop>::drop
|
||||
at RUSTLIB/alloc/src/boxed.rs:LL:CC
|
||||
1: std::ptr::drop_glue))
|
||||
1: std::ptr::drop_glue::<std::boxed::Box<Mutex>> - shim(Some(std::boxed::Box<Mutex>))
|
||||
at RUSTLIB/core/src/ptr/mod.rs:LL:CC
|
||||
2: std::mem::drop
|
||||
2: std::mem::drop::<std::boxed::Box<Mutex>>
|
||||
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
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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::<std::ffi::c_void>(), 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::<std::ffi::c_void>(), 4);
|
||||
assert!(res <= 3);
|
||||
buf.assume_init(); //~ERROR: encountered uninitialized memory, but expected an integer
|
||||
assert_eq!(libc::close(fd), 0);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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::<impl std::alloc::GlobalAlloc for std::alloc::System>::dealloc
|
||||
at RUSTLIB/std/src/sys/alloc/PLATFORM.rs:LL:CC
|
||||
1: <std::alloc::System as std::alloc::Allocator>::deallocate
|
||||
at RUSTLIB/std/src/alloc.rs:LL:CC
|
||||
|
||||
@@ -9,9 +9,9 @@ LL | self.1.deallocate(From::from(ptr.cast()), layout);
|
||||
= note: stack backtrace:
|
||||
0: <std::boxed::Box<i32> as std::ops::Drop>::drop
|
||||
at RUSTLIB/alloc/src/boxed.rs:LL:CC
|
||||
1: std::ptr::drop_glue))
|
||||
1: std::ptr::drop_glue::<std::boxed::Box<i32>> - shim(Some(std::boxed::Box<i32>))
|
||||
at RUSTLIB/core/src/ptr/mod.rs:LL:CC
|
||||
2: std::mem::drop
|
||||
2: std::mem::drop::<std::boxed::Box<i32>>
|
||||
at RUSTLIB/core/src/mem/mod.rs:LL:CC
|
||||
3: main
|
||||
at tests/fail/alloc/stack_free.rs:LL:CC
|
||||
|
||||
@@ -20,11 +20,11 @@ help: the protected tag <TAG> was created here, in the initial state Frozen
|
||||
LL | fn safe(x: &i32, y: &mut Cell<i32>) {
|
||||
| ^
|
||||
= note: stack backtrace:
|
||||
0: std::mem::replace
|
||||
0: std::mem::replace::<i32>
|
||||
at RUSTLIB/core/src/mem/mod.rs:LL:CC
|
||||
1: std::cell::Cell::replace
|
||||
1: std::cell::Cell::<i32>::replace
|
||||
at RUSTLIB/core/src/cell.rs:LL:CC
|
||||
2: std::cell::Cell::set
|
||||
2: std::cell::Cell::<i32>::set
|
||||
at RUSTLIB/core/src/cell.rs:LL:CC
|
||||
3: safe
|
||||
at tests/fail/both_borrows/aliasing_mut4.rs:LL:CC
|
||||
|
||||
@@ -23,7 +23,7 @@ help: <TAG> 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::<i32>
|
||||
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
|
||||
|
||||
@@ -19,11 +19,11 @@ LL | core::ptr::drop_in_place(x);
|
||||
= note: stack backtrace:
|
||||
0: <HasDrop as std::ops::Drop>::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::<HasDrop> - 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
|
||||
|
||||
@@ -22,11 +22,11 @@ LL | core::ptr::drop_in_place(x);
|
||||
= note: stack backtrace:
|
||||
0: <HasDrop as std::ops::Drop>::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::<HasDrop> - 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
|
||||
|
||||
@@ -17,13 +17,13 @@ help: <TAG> 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::<i32>::from_raw_in
|
||||
at RUSTLIB/alloc/src/boxed.rs:LL:CC
|
||||
1: std::boxed::Box::from_raw
|
||||
1: std::boxed::Box::<i32>::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
|
||||
|
||||
@@ -28,13 +28,13 @@ LL | || drop(Box::from_raw(ptr)),
|
||||
= note: stack backtrace:
|
||||
0: <std::boxed::Box<i32> as std::ops::Drop>::drop
|
||||
at RUSTLIB/alloc/src/boxed.rs:LL:CC
|
||||
1: std::ptr::drop_glue))
|
||||
1: std::ptr::drop_glue::<std::boxed::Box<i32>> - shim(Some(std::boxed::Box<i32>))
|
||||
at RUSTLIB/core/src/ptr/mod.rs:LL:CC
|
||||
2: std::mem::drop
|
||||
2: std::mem::drop::<std::boxed::Box<i32>>
|
||||
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
|
||||
|
||||
@@ -17,13 +17,13 @@ help: <TAG> 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::<i32>::from_raw_in
|
||||
at RUSTLIB/alloc/src/boxed.rs:LL:CC
|
||||
1: std::boxed::Box::from_raw
|
||||
1: std::boxed::Box::<i32>::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
|
||||
|
||||
@@ -28,13 +28,13 @@ LL | || drop(Box::from_raw(ptr)),
|
||||
= note: stack backtrace:
|
||||
0: <std::boxed::Box<i32> as std::ops::Drop>::drop
|
||||
at RUSTLIB/alloc/src/boxed.rs:LL:CC
|
||||
1: std::ptr::drop_glue))
|
||||
1: std::ptr::drop_glue::<std::boxed::Box<i32>> - shim(Some(std::boxed::Box<i32>))
|
||||
at RUSTLIB/core/src/ptr/mod.rs:LL:CC
|
||||
2: std::mem::drop
|
||||
2: std::mem::drop::<std::boxed::Box<i32>>
|
||||
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
|
||||
|
||||
@@ -21,7 +21,7 @@ LL | }; // *deallocate* coroutine_iterator
|
||||
at tests/fail/coroutine-pinned-moved.rs:LL:CC
|
||||
1: <CoroutineIteratorAdapter<{static coroutine@tests/fail/coroutine-pinned-moved.rs:LL:CC}> as std::iter::Iterator>::next
|
||||
at tests/fail/coroutine-pinned-moved.rs:LL:CC
|
||||
2: std::boxed::iter::next
|
||||
2: std::boxed::iter::<impl std::iter::Iterator for std::boxed::Box<CoroutineIteratorAdapter<{static coroutine@tests/fail/coroutine-pinned-moved.rs:LL:CC}>>>::next
|
||||
at RUSTLIB/alloc/src/boxed/iter.rs:LL:CC
|
||||
3: main
|
||||
at tests/fail/coroutine-pinned-moved.rs:LL:CC
|
||||
|
||||
@@ -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::<std::cell::RefCell<std::option::Option<Dummy>>>::new
|
||||
at RUSTLIB/alloc/src/rc.rs:LL:CC
|
||||
1: main
|
||||
at tests/fail/memleak_rc.rs:LL:CC
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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::<i32, {closure@std::sys::fs::PLATFORM::File::open_c::{closure#0}}>
|
||||
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::<std::sys::fs::PLATFORM::File>
|
||||
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::<std::sys::fs::PLATFORM::File>
|
||||
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::<std::sys::fs::PLATFORM::File>
|
||||
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
|
||||
|
||||
@@ -9,9 +9,9 @@ LL | self.1.deallocate(From::from(ptr.cast()), layout);
|
||||
= note: stack backtrace:
|
||||
0: <std::boxed::Box<i32> as std::ops::Drop>::drop
|
||||
at RUSTLIB/alloc/src/boxed.rs:LL:CC
|
||||
1: std::ptr::drop_glue))
|
||||
1: std::ptr::drop_glue::<std::boxed::Box<i32>> - shim(Some(std::boxed::Box<i32>))
|
||||
at RUSTLIB/core/src/ptr/mod.rs:LL:CC
|
||||
2: std::mem::drop
|
||||
2: std::mem::drop::<std::boxed::Box<i32>>
|
||||
at RUSTLIB/core/src/mem/mod.rs:LL:CC
|
||||
3: main::{closure#0}
|
||||
at tests/fail/stacked_borrows/deallocate_against_protector1.rs:LL:CC
|
||||
|
||||
@@ -12,7 +12,7 @@ help: <TAG> 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::<u8>
|
||||
at RUSTLIB/core/src/ptr/mod.rs:LL:CC
|
||||
1: main
|
||||
at tests/fail/stacked_borrows/drop_in_place_retag.rs:LL:CC
|
||||
|
||||
@@ -9,29 +9,29 @@ LL | extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
|
||||
= note: stack backtrace:
|
||||
0: <fn() as std::ops::FnOnce<()>>::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::<fn(), ()>
|
||||
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::<impl std::ops::FnOnce<()> 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, &dyn std::ops::Fn() -> 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::<isize, {closure@std::rt::lang_start_internal::{closure#0}}>
|
||||
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
|
||||
|
||||
@@ -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::<std::cell::Cell<std::option::Option<&i32>>>::try_with
|
||||
1: std::thread::LocalKey::<std::cell::Cell<std::option::Option<&i32>>>::try_with::<{closure@tests/fail/tls_macro_leak.rs:LL:CC}, ()>
|
||||
at RUSTLIB/std/src/thread/local.rs:LL:CC
|
||||
2: std::thread::LocalKey::<std::cell::Cell<std::option::Option<&i32>>>::with
|
||||
2: std::thread::LocalKey::<std::cell::Cell<std::option::Option<&i32>>>::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
|
||||
|
||||
@@ -20,7 +20,7 @@ help: the protected tag <TAG> was created here, in the initial state Reserved
|
||||
LL | fn dereference<T>(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
|
||||
|
||||
@@ -20,7 +20,7 @@ help: the protected tag <TAG> was created here, in the initial state Reserved
|
||||
LL | fn dereference<T>(x: T, y: *mut u8) -> T {
|
||||
| ^
|
||||
= note: stack backtrace:
|
||||
0: dereference
|
||||
0: dereference::<std::boxed::Box<u8>>
|
||||
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
|
||||
|
||||
@@ -20,7 +20,7 @@ help: the protected tag <TAG> was created here, in the initial state Reserved
|
||||
LL | fn dereference<T>(x: T, y: *mut u8) -> T {
|
||||
| ^
|
||||
= note: stack backtrace:
|
||||
0: dereference
|
||||
0: dereference::<&mut std::cell::UnsafeCell<u8>>
|
||||
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
|
||||
|
||||
@@ -21,9 +21,9 @@ LL | fn inner(x: &mut i32, f: fn(*mut i32)) {
|
||||
= note: stack backtrace:
|
||||
0: <std::boxed::Box<i32> as std::ops::Drop>::drop
|
||||
at RUSTLIB/alloc/src/boxed.rs:LL:CC
|
||||
1: std::ptr::drop_glue))
|
||||
1: std::ptr::drop_glue::<std::boxed::Box<i32>> - shim(Some(std::boxed::Box<i32>))
|
||||
at RUSTLIB/core/src/ptr/mod.rs:LL:CC
|
||||
2: std::mem::drop
|
||||
2: std::mem::drop::<std::boxed::Box<i32>>
|
||||
at RUSTLIB/core/src/mem/mod.rs:LL:CC
|
||||
3: main::{closure#0}
|
||||
at tests/fail/tree_borrows/strongly-protected.rs:LL:CC
|
||||
|
||||
@@ -21,9 +21,9 @@ LL | fn inner(x: &mut i32, f: fn(usize)) {
|
||||
= note: stack backtrace:
|
||||
0: <std::boxed::Box<i32> as std::ops::Drop>::drop
|
||||
at RUSTLIB/alloc/src/boxed.rs:LL:CC
|
||||
1: std::ptr::drop_glue))
|
||||
1: std::ptr::drop_glue::<std::boxed::Box<i32>> - shim(Some(std::boxed::Box<i32>))
|
||||
at RUSTLIB/core/src/ptr/mod.rs:LL:CC
|
||||
2: std::mem::drop
|
||||
2: std::mem::drop::<std::boxed::Box<i32>>
|
||||
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
|
||||
|
||||
@@ -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::<PartialDrop>
|
||||
at RUSTLIB/core/src/ptr/mod.rs:LL:CC
|
||||
1: main
|
||||
at tests/fail/unaligned_pointers/drop_in_place.rs:LL:CC
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -9,7 +9,7 @@ LL | let mut order = unsafe { compare_bytes(left, right, len) as isize }
|
||||
= note: stack backtrace:
|
||||
0: <u8 as core::slice::cmp::SliceOrd>::compare
|
||||
at RUSTLIB/core/src/slice/cmp.rs:LL:CC
|
||||
1: core::slice::cmp::cmp
|
||||
1: core::slice::cmp::<impl std::cmp::Ord for [u8]>::cmp
|
||||
at RUSTLIB/core/src/slice/cmp.rs:LL:CC
|
||||
2: main
|
||||
at tests/fail/uninit/uninit_alloc_diagnostic.rs:LL:CC
|
||||
|
||||
@@ -9,7 +9,7 @@ LL | let mut order = unsafe { compare_bytes(left, right, len) as isize }
|
||||
= note: stack backtrace:
|
||||
0: <u8 as core::slice::cmp::SliceOrd>::compare
|
||||
at RUSTLIB/core/src/slice/cmp.rs:LL:CC
|
||||
1: core::slice::cmp::cmp
|
||||
1: core::slice::cmp::<impl std::cmp::Ord for [u8]>::cmp
|
||||
at RUSTLIB/core/src/slice/cmp.rs:LL:CC
|
||||
2: main
|
||||
at tests/fail/uninit/uninit_alloc_diagnostic_with_provenance.rs:LL:CC
|
||||
|
||||
@@ -25,7 +25,7 @@ LL | dealloc(ptr as *mut u8, Layout::new::<u64>())
|
||||
at tests/genmc/fail/atomics/atomic_ptr_double_free.rs:LL:CC
|
||||
2: <std::boxed::Box<{closure@tests/genmc/fail/atomics/atomic_ptr_double_free.rs:LL:CC}> 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
|
||||
|
||||
@@ -13,7 +13,7 @@ LL | dealloc(b as *mut u8, Layout::new::<u64>());
|
||||
at tests/genmc/fail/data_race/atomic_ptr_alloc_race.rs:LL:CC
|
||||
1: <std::boxed::Box<{closure@tests/genmc/fail/data_race/atomic_ptr_alloc_race.rs:LL:CC}> 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
|
||||
|
||||
@@ -13,7 +13,7 @@ LL | *b = 42;
|
||||
at tests/genmc/fail/data_race/atomic_ptr_alloc_race.rs:LL:CC
|
||||
1: <std::boxed::Box<{closure@tests/genmc/fail/data_race/atomic_ptr_alloc_race.rs:LL:CC}> 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
|
||||
|
||||
@@ -23,7 +23,7 @@ LL | }),
|
||||
at tests/genmc/fail/data_race/atomic_ptr_dealloc_write_race.rs:LL:CC
|
||||
1: <std::boxed::Box<{closure@tests/genmc/fail/data_race/atomic_ptr_dealloc_write_race.rs:LL:CC}> 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
|
||||
|
||||
@@ -13,7 +13,7 @@ LL | }),
|
||||
at tests/genmc/fail/data_race/atomic_ptr_write_dealloc_race.rs:LL:CC
|
||||
1: <std::boxed::Box<{closure@tests/genmc/fail/data_race/atomic_ptr_write_dealloc_race.rs:LL:CC}> 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
|
||||
|
||||
@@ -13,7 +13,7 @@ LL | X = 2;
|
||||
at tests/genmc/fail/data_race/mpu2_rels_rlx.rs:LL:CC
|
||||
1: <std::boxed::Box<{closure@tests/genmc/fail/data_race/mpu2_rels_rlx.rs:LL:CC}> 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
|
||||
|
||||
@@ -13,7 +13,7 @@ LL | X = 2;
|
||||
at tests/genmc/fail/data_race/weak_orderings.rs:LL:CC
|
||||
1: <std::boxed::Box<{closure@tests/genmc/fail/data_race/weak_orderings.rs:LL:CC}> 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
|
||||
|
||||
@@ -13,7 +13,7 @@ LL | X = 2;
|
||||
at tests/genmc/fail/data_race/weak_orderings.rs:LL:CC
|
||||
1: <std::boxed::Box<{closure@tests/genmc/fail/data_race/weak_orderings.rs:LL:CC}> 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
|
||||
|
||||
@@ -13,7 +13,7 @@ LL | X = 2;
|
||||
at tests/genmc/fail/data_race/weak_orderings.rs:LL:CC
|
||||
1: <std::boxed::Box<{closure@tests/genmc/fail/data_race/weak_orderings.rs:LL:CC}> 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
|
||||
|
||||
@@ -11,11 +11,11 @@ LL | self.lock.inner.unlock();
|
||||
= note: stack backtrace:
|
||||
0: <std::sync::MutexGuard<'_, u64> 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::<std::sync::MutexGuard<'_, u64>> - 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::<EvilSend<std::sync::MutexGuard<'_, u64>>> - shim(Some(EvilSend<std::sync::MutexGuard<'_, u64>>))
|
||||
at RUSTLIB/core/src/ptr/mod.rs:LL:CC
|
||||
3: std::mem::drop
|
||||
3: std::mem::drop::<EvilSend<std::sync::MutexGuard<'_, u64>>>
|
||||
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
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user