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:
Jonathan Brouwer
2026-05-10 19:05:41 +02:00
committed by GitHub
135 changed files with 2747 additions and 1015 deletions
-1
View File
@@ -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
View File
@@ -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
View File
@@ -1,5 +1,4 @@
[package]
authors = ["Miri Team"]
license = "MIT OR Apache-2.0"
name = "genmc-sys"
version = "0.1.0"
-1
View File
@@ -1,5 +1,4 @@
[package]
authors = ["Miri Team"]
description = "Helpers for miri maintenance"
license = "MIT OR Apache-2.0"
name = "miri-script"
+5 -1
View File
@@ -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
View File
@@ -1 +1 @@
44860d3e9ef700cac0b4a61d924f41f46bf1b447
63b1dfc0e00fd6f8ad7cd8817fc712e7d9b7be59
+265 -115
View File
@@ -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(())
}
}
}
+2 -1
View File
@@ -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);
+33 -24
View File
@@ -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.
+23
View File
@@ -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![],
};
+1 -2
View File
@@ -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.
+4 -1
View File
@@ -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 _,
+6 -3
View File
@@ -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"),
}
}
}
+8
View File
@@ -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>>);
+105 -11
View File
@@ -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);
+2 -2
View File
@@ -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 {
+1
View File
@@ -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)?,
+2 -2
View File
@@ -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());
}
}
+51 -1
View File
@@ -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" => {
+88 -39
View File
@@ -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 -1
View File
@@ -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 _;
+287 -348
View File
@@ -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" => {
+17 -19
View File
@@ -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
View File
@@ -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
@@ -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
@@ -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
@@ -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
+1 -1
View File
@@ -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