Auto merge of #156118 - RalfJung:miri, r=RalfJung

miri subtree update

Subtree update of `miri` to https://github.com/rust-lang/miri/commit/0853747bf78b8b6e314dcdf14ade9c83a6c54f3e.

Created using https://github.com/rust-lang/josh-sync.

r? @ghost
This commit is contained in:
bors
2026-05-03 22:28:16 +00:00
29 changed files with 442 additions and 108 deletions
+6 -6
View File
@@ -58,7 +58,7 @@ jobs:
env:
HOST_TARGET: ${{ matrix.host_target }}
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: install multiarch
if: ${{ matrix.multiarch != '' }}
run: |
@@ -105,7 +105,7 @@ jobs:
name: style checks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- uses: ./.github/workflows/setup
- name: rustfmt
@@ -121,7 +121,7 @@ jobs:
name: bootstrap build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
# Deliberately skipping `./.github/workflows/setup` as we do our own setup
- name: Add cache for cargo
id: cache
@@ -156,7 +156,7 @@ jobs:
name: coverage report
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- uses: ./.github/workflows/setup
- name: coverage
run: ./miri test --coverage
@@ -191,7 +191,7 @@ jobs:
pull-requests: write
if: ${{ github.event_name == 'schedule' }}
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
fetch-depth: 256 # get a bit more of the history
- name: install josh-sync
@@ -205,7 +205,7 @@ jobs:
- name: Install rustup-toolchain-install-master
run: cargo install -f rustup-toolchain-install-master
# Create a token for the next step so it can create a PR that actually runs CI.
- uses: actions/create-github-app-token@v2
- uses: actions/create-github-app-token@v3
id: app-token
with:
app-id: ${{ vars.APP_CLIENT_ID }}
+3 -3
View File
@@ -13,7 +13,7 @@ jobs:
name: Build the sysroots
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Build the sysroots
run: |
rustup toolchain install nightly
@@ -25,7 +25,7 @@ jobs:
- name: Upload build errors
# We don't want to skip this step on failure
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: failures
path: failures.tar.gz
@@ -38,7 +38,7 @@ jobs:
steps:
# Download our build error logs
- name: Download build errors
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: failures
# Send a Zulip notification
+1 -1
View File
@@ -1 +1 @@
9836b06b55f5389f605ee7766eeecd9f17a86cb5
44860d3e9ef700cac0b4a61d924f41f46bf1b447
@@ -1,4 +1,5 @@
use rustc_abi::Size;
use rustc_hir::find_attr;
use rustc_middle::mir::Mutability;
use rustc_middle::ty::layout::HasTypingEnv;
use rustc_middle::ty::{self, Ty};
@@ -137,16 +138,23 @@ impl<'tcx> NewPermission {
let ty_is_freeze = pointee.is_freeze(*cx.tcx, cx.typing_env());
let is_protected = mode == RetagMode::FnEntry;
// Check if the implicit writes check has been enabled for this function using the `-Zmiri-tree-borrows-implicit-writes` flag
let implicit_writes = cx
.machine
.borrow_tracker
.as_ref()
.unwrap()
.borrow()
.borrow_tracker_method
.get_tree_borrows_params()
.implicit_writes;
// Check if the implicit writes feature is globally enabled, using the
// `-Zmiri-tree-borrows-implicit-writes` flag, and not locally disabled using the
// `#[rustc_no_writable]` attribute. For performance reasons, only performs the lookup if
// is_protected is true as implicit writes are only performed for protected references.
let implicit_writes_enabled = is_protected && {
let implicit_writes = cx
.machine
.borrow_tracker
.as_ref()
.unwrap()
.borrow()
.borrow_tracker_method
.get_tree_borrows_params()
.implicit_writes;
let def_id = cx.frame().instance().def_id();
implicit_writes && !find_attr!(cx.tcx, def_id, RustcNoWritable)
};
if matches!(ref_mutability, Some(Mutability::Mut) | None if !ty_is_unpin) {
// Mutable reference / Box to pinning type: retagging is a NOP.
@@ -179,7 +187,7 @@ impl<'tcx> NewPermission {
},
// Mutable references
Some(Mutability::Mut) => {
if is_protected && implicit_writes && !matches!(part, Outside) {
if implicit_writes_enabled && !matches!(part, Outside) {
// We cannot use `Unique` for the outside part.
Permission::new_unique()
} else if is_protected || frozen {
@@ -191,8 +199,8 @@ impl<'tcx> NewPermission {
}
}
// Boxes
None =>
if is_protected && implicit_writes && !matches!(part, Outside) {
None => {
if implicit_writes_enabled && !matches!(part, Outside) {
// Boxes are treated the same as mutable references.
Permission::new_unique()
} else if is_protected || frozen {
@@ -201,7 +209,8 @@ impl<'tcx> NewPermission {
Permission::new_reserved_frz()
} else {
Permission::new_reserved_im()
},
}
}
}
};
@@ -477,15 +486,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
mode: RetagMode,
) -> InterpResult<'tcx, Option<ImmTy<'tcx>>> {
let this = self.eval_context_mut();
let new_perm = match ty.kind() {
let new_perm = match *ty.kind() {
_ if ty.is_box_global(*this.tcx) => {
// The `None` marks this as a Box.
NewPermission::new(ty.builtin_deref(true).unwrap(), None, mode, this)
}
&ty::Ref(_, pointee, mutability) =>
ty::Ref(_, pointee, mutability) =>
NewPermission::new(pointee, Some(mutability), mode, this),
&ty::RawPtr(..) => {
ty::RawPtr(..) => {
assert!(mode == RetagMode::Raw);
// We don't give new tags to raw pointers.
None
@@ -219,7 +219,7 @@ pub trait EvalContextExt<'tcx>: MiriInterpCxExt<'tcx> {
let this = self.eval_context_mut();
this.machine.blocking_io.register(
source_fd,
InterestReceiver::UnblockThread(this.machine.threads.active_thread()),
InterestReceiver::UnblockThread(this.active_thread()),
interests,
);
this.block_thread(BlockReason::IO, timeout, callback);
+29 -16
View File
@@ -90,7 +90,7 @@ impl From<ThreadId> for u64 {
}
/// Keeps track of what the thread is blocked on.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BlockReason {
/// The thread tried to join the specified thread and is blocked until that
/// thread terminates.
@@ -151,8 +151,8 @@ impl<'tcx> ThreadState<'tcx> {
matches!(self, ThreadState::Terminated)
}
fn is_blocked_on(&self, reason: BlockReason) -> bool {
matches!(*self, ThreadState::Blocked { reason: actual_reason, .. } if actual_reason == reason)
fn is_blocked_on(&self, reason: &BlockReason) -> bool {
matches!(self, ThreadState::Blocked { reason: actual_reason, .. } if actual_reason == reason)
}
}
@@ -421,9 +421,14 @@ pub enum TimeoutAnchor {
Absolute,
}
/// An error signaling that the requested thread doesn't exist.
/// An error signaling that the requested thread doesn't exist or has terminated.
#[derive(Debug, Copy, Clone)]
pub struct ThreadNotFound;
pub enum ThreadLookupError {
/// No thread with this ID exists.
InvalidId,
/// The thread exists but has already terminated.
Terminated(ThreadId),
}
/// A set of threads.
#[derive(Debug)]
@@ -489,13 +494,21 @@ impl<'tcx> ThreadManager<'tcx> {
}
}
pub fn thread_id_try_from(&self, id: impl TryInto<u32>) -> Result<ThreadId, ThreadNotFound> {
/// Returns the `ThreadId` for the given raw thread id.
/// Returns `Err(ThreadNotFound::InvalidId)` if the id is out of range, or
/// `Err(ThreadNotFound::Terminated(id))` if the thread exists but has terminated.
pub fn thread_id_try_from(&self, id: impl TryInto<u32>) -> Result<ThreadId, ThreadLookupError> {
if let Ok(id) = id.try_into()
&& usize::try_from(id).is_ok_and(|id| id < self.threads.len())
{
Ok(ThreadId(id))
let thread_id = ThreadId(id);
if self.threads[thread_id].state.is_terminated() {
Err(ThreadLookupError::Terminated(thread_id))
} else {
Ok(thread_id)
}
} else {
Err(ThreadNotFound)
Err(ThreadLookupError::InvalidId)
}
}
@@ -696,7 +709,7 @@ trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> {
// If a thread is blocked on GenMC, we have to implicitly unblock it when it gets scheduled again.
if this.machine.threads.threads[next_thread_id]
.state
.is_blocked_on(BlockReason::Genmc)
.is_blocked_on(&BlockReason::Genmc)
{
info!(
"GenMC: scheduling blocked thread {next_thread_id:?}, so we unblock it now."
@@ -798,7 +811,7 @@ trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> {
} else if thread_manager
.threads
.iter()
.any(|thread| thread.state.is_blocked_on(BlockReason::IO))
.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
@@ -884,7 +897,7 @@ trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> {
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
#[inline]
fn thread_id_try_from(&self, id: impl TryInto<u32>) -> Result<ThreadId, ThreadNotFound> {
fn thread_id_try_from(&self, id: impl TryInto<u32>) -> Result<ThreadId, ThreadLookupError> {
self.eval_context_ref().machine.threads.thread_id_try_from(id)
}
@@ -1059,11 +1072,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let threads = &this.machine.threads.threads;
let joining_threads = threads
.iter_enumerated()
.filter(|(_, thread)| thread.state.is_blocked_on(unblock_reason))
.filter(|(_, thread)| thread.state.is_blocked_on(&unblock_reason))
.map(|(id, _)| id)
.collect::<Vec<_>>();
for thread in joining_threads {
this.unblock_thread(thread, unblock_reason)?;
this.unblock_thread(thread, unblock_reason.clone())?;
}
interp_ok(())
@@ -1231,9 +1244,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// Sanity check `join_status`.
assert!(
threads
.iter()
.all(|thread| { !thread.state.is_blocked_on(BlockReason::Join(joined_thread_id)) }),
threads.iter().all(|thread| {
!thread.state.is_blocked_on(&BlockReason::Join(joined_thread_id))
}),
"this thread already has threads waiting for its termination"
);
+15 -15
View File
@@ -1,3 +1,4 @@
use std::assert_matches;
use std::ffi::{OsStr, OsString};
use rustc_data_structures::fx::FxHashMap;
@@ -108,23 +109,22 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
if this.machine.communicate() { std::process::id() } else { 1000 }
}
/// Get an "OS" thread ID for the current thread.
fn get_current_tid(&self) -> u32 {
let this = self.eval_context_ref();
self.get_tid(this.machine.threads.active_thread())
}
/// Get an "OS" thread ID for any thread.
fn get_tid(&self, thread: ThreadId) -> u32 {
let this = self.eval_context_ref();
let index = thread.to_u32();
let target_os = &this.tcx.sess.target.os;
if matches!(target_os, Os::Linux | Os::Android) {
// On Linux, the main thread has PID == TID so we uphold this.
this.get_pid().strict_add(index)
} else {
// Other platforms do not display any relationship between PID and TID.
index
}
assert!(this.target_os_is_unix());
// On Linux, the main thread has PID == TID so we uphold this. For simplicity we do it
// everywhere. That also ensures this ID is different from what is returned by
// `pthread_self`.
this.get_pid().strict_add(thread.to_u32())
}
/// Convert TID back to a `ThreadId`, or `None` if it is invalid or the thread has terminated.
fn get_thread_id_from_linux_tid(&self, tid: u32) -> Option<ThreadId> {
let this = self.eval_context_ref();
assert_matches!(this.tcx.sess.target.os, Os::Linux | Os::Android);
// TID = PID + thread_index => index = TID - PID.
let id = tid.checked_sub(this.get_pid())?;
this.machine.threads.thread_id_try_from(id).ok()
}
}
+7 -1
View File
@@ -459,7 +459,13 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
)?;
let thread = this.read_target_usize(thread_id)?;
if let Ok(thread) = this.thread_id_try_from(thread) {
// Joining a terminated thread is valid.
use crate::concurrency::thread::ThreadLookupError;
let thread = match this.thread_id_try_from(thread) {
Ok(id) | Err(ThreadLookupError::Terminated(id)) => Some(id),
Err(ThreadLookupError::InvalidId) => None,
};
if let Some(thread) = thread {
this.join_thread_exclusive(
thread,
/* success_retval */ Scalar::from_bool(true),
+1
View File
@@ -251,6 +251,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
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;
if target.families.iter().any(|f| f == "unix") {
for &(name, kind) in UNIX_IO_ERROR_TABLE {
if err.kind() == kind {
+3 -2
View File
@@ -269,7 +269,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// For most platforms the return type is an `i32`, but some are unsigned. The TID
// will always be positive so we don't need to differentiate.
interp_ok(Scalar::from_u32(this.get_current_tid()))
interp_ok(Scalar::from_u32(this.get_tid(this.active_thread())))
}
/// `fields_size`, if present, says how large each field of the struct is.
@@ -323,7 +323,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
/// allows querying the ID for arbitrary threads, identified by their pthread_t.
///
/// API documentation: <https://www.manpagez.com/man/3/pthread_threadid_np/>.
fn apple_pthread_threadip_np(
fn apple_pthread_threadid_np(
&mut self,
thread_op: &OpTy<'tcx>,
tid_op: &OpTy<'tcx>,
@@ -349,6 +349,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
thread
};
// This returns an `int`, not a `pthread_t`, so we treat it like we treat `gettid` on Linux.
let tid = this.get_tid(thread);
let tid_dest = this.deref_pointer_as(tid_op, this.machine.layouts.u64)?;
this.write_int(tid, &tid_dest)?;
+48 -14
View File
@@ -180,6 +180,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
&[Os::Linux, Os::Android, Os::MacOs, Os::Solaris, Os::Illumos],
link_name,
)?;
let [uname] = this.check_shim_sig(
shim_sig!(extern "C" fn(*mut _) -> i32),
link_name,
@@ -296,6 +297,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
"flock" => {
// Currently this function does not exist on all Unixes, e.g. on Solaris.
this.check_target_os(&[Os::Linux, Os::FreeBsd, Os::MacOs, Os::Illumos], link_name)?;
let [fd, op] = this.check_shim_sig(
shim_sig!(extern "C" fn(i32, i32) -> i32),
link_name,
@@ -495,6 +497,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
&[Os::Linux, Os::FreeBsd, Os::Solaris, Os::Illumos, Os::Android],
link_name,
)?;
let [fd, offset, len] = this.check_shim_sig(
shim_sig!(extern "C" fn(i32, libc::off_t, libc::off_t) -> i32),
link_name,
@@ -560,6 +563,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
&[Os::Linux, Os::Android, Os::FreeBsd, Os::Solaris, Os::Illumos],
link_name,
)?;
let [pipefd, flags] = this.check_shim_sig(
shim_sig!(extern "C" fn(*mut _, i32) -> i32),
link_name,
@@ -676,6 +680,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
)?;
this.getpeername(socket, address, address_len, dest)?;
}
"shutdown" => {
let [sockfd, how] = this.check_shim_sig(
shim_sig!(extern "C" fn(i32, i32) -> i32),
link_name,
abi,
args,
)?;
let result = this.shutdown(sockfd, how)?;
this.write_scalar(result, dest)?;
}
// Time
"gettimeofday" => {
@@ -733,6 +747,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
"reallocarray" => {
// Currently this function does not exist on all Unixes, e.g. on macOS.
this.check_target_os(&[Os::Linux, Os::FreeBsd, Os::Android], link_name)?;
let [ptr, nmemb, size] =
this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
let ptr = this.read_pointer(ptr)?;
@@ -1010,6 +1025,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
&[Os::FreeBsd, Os::Linux, Os::Android, Os::Solaris, Os::Illumos],
link_name,
)?;
let [clock_id, flags, req, rem] =
this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
let result = this.clock_nanosleep(clock_id, flags, req, rem)?;
@@ -1018,19 +1034,26 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
"sched_getaffinity" => {
// Currently this function does not exist on all Unixes, e.g. on macOS.
this.check_target_os(&[Os::Linux, Os::FreeBsd, Os::Android], link_name)?;
let [pid, cpusetsize, mask] =
this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
let pid = this.read_scalar(pid)?.to_u32()?;
let cpusetsize = this.read_target_usize(cpusetsize)?;
let mask = this.read_pointer(mask)?;
// TODO: when https://github.com/rust-lang/miri/issues/3730 is fixed this should use its notion of tid/pid
let thread_id = match pid {
0 => this.active_thread(),
_ =>
throw_unsup_format!(
"`sched_getaffinity` is only supported with a pid of 0 (indicating the current thread)"
),
let thread_id = if pid == 0 {
this.active_thread()
} else if matches!(this.tcx.sess.target.os, Os::Linux | Os::Android) {
// On Linux/Android, pid can be a TID as returned by `gettid`.
let Some(thread_id) = this.get_thread_id_from_linux_tid(pid) else {
this.set_last_error_and_return(LibcError("ESRCH"), dest)?;
return interp_ok(EmulateItemResult::NeedsReturn);
};
thread_id
} else {
throw_unsup_format!(
"`sched_getaffinity` is only supported with a pid of 0 (indicating the current thread) on non-Linux platforms"
)
};
// The mask is stored in chunks, and the size must be a whole number of chunks.
@@ -1056,19 +1079,26 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
"sched_setaffinity" => {
// Currently this function does not exist on all Unixes, e.g. on macOS.
this.check_target_os(&[Os::Linux, Os::FreeBsd, Os::Android], link_name)?;
let [pid, cpusetsize, mask] =
this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
let pid = this.read_scalar(pid)?.to_u32()?;
let cpusetsize = this.read_target_usize(cpusetsize)?;
let mask = this.read_pointer(mask)?;
// TODO: when https://github.com/rust-lang/miri/issues/3730 is fixed this should use its notion of tid/pid
let thread_id = match pid {
0 => this.active_thread(),
_ =>
throw_unsup_format!(
"`sched_setaffinity` is only supported with a pid of 0 (indicating the current thread)"
),
let thread_id = if pid == 0 {
this.active_thread()
} else if matches!(this.tcx.sess.target.os, Os::Linux | Os::Android) {
// On Linux/Android, pid can be a TID as returned by `gettid`.
let Some(thread_id) = this.get_thread_id_from_linux_tid(pid) else {
this.set_last_error_and_return(LibcError("ESRCH"), dest)?;
return interp_ok(EmulateItemResult::NeedsReturn);
};
thread_id
} else {
throw_unsup_format!(
"`sched_setaffinity` is only supported with a pid of 0 (indicating the current thread) on non-Linux platforms"
)
};
if this.ptr_is_null(mask)? {
@@ -1118,6 +1148,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
&[Os::Linux, Os::MacOs, Os::FreeBsd, Os::Illumos, Os::Solaris, Os::Android],
link_name,
)?;
let [buf, bufsize] =
this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
let buf = this.read_pointer(buf)?;
@@ -1150,6 +1181,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
&[Os::Linux, Os::FreeBsd, Os::Illumos, Os::Solaris, Os::Android],
link_name,
)?;
let [ptr, len, flags] =
this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
let ptr = this.read_pointer(ptr)?;
@@ -1163,6 +1195,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// This function is non-standard but exists with the same signature and
// same behavior (eg never fails) on FreeBSD and Solaris/Illumos.
this.check_target_os(&[Os::FreeBsd, Os::Illumos, Os::Solaris], link_name)?;
let [ptr, len] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
let ptr = this.read_pointer(ptr)?;
let len = this.read_target_usize(len)?;
@@ -1186,6 +1219,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
&[Os::Linux, Os::FreeBsd, Os::Illumos, Os::Solaris, Os::Android, Os::MacOs],
link_name,
)?;
// This function looks and behaves exactly like miri_start_unwind.
let [payload] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
this.handle_miri_start_unwind(payload)?;
@@ -244,7 +244,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
"pthread_threadid_np" => {
let [thread, tid_ptr] =
this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
let res = this.apple_pthread_threadip_np(thread, tid_ptr)?;
let res = this.apple_pthread_threadid_np(thread, tid_ptr)?;
this.write_scalar(res, dest)?;
}
+52 -1
View File
@@ -1,6 +1,6 @@
use std::cell::{Cell, RefCell};
use std::io::Read;
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
use std::net::{Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr, SocketAddrV4, SocketAddrV6};
use std::time::Duration;
use std::{io, iter};
@@ -1053,6 +1053,48 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
),
)
}
fn shutdown(&mut self, socket: &OpTy<'tcx>, how: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
let this = self.eval_context_mut();
let socket = this.read_scalar(socket)?.to_i32()?;
let how = this.read_scalar(how)?.to_i32()?;
// 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"));
};
assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");
let state = socket.state.borrow();
let (SocketState::Connecting(stream) | SocketState::Connected(stream)) = &*state else {
return this.set_last_error_and_return_i32(LibcError("ENOTCONN"));
};
let shut_rd = this.eval_libc_i32("SHUT_RD");
let shut_wr = this.eval_libc_i32("SHUT_WR");
let shut_rdwr = this.eval_libc_i32("SHUT_RDWR");
let how = match () {
_ if how == shut_rd => Shutdown::Read,
_ if how == shut_wr => Shutdown::Write,
_ if how == shut_rdwr => Shutdown::Both,
// An invalid value was passed to `how`.
_ => return this.set_last_error_and_return_i32(LibcError("EINVAL")),
};
match stream.shutdown(how) {
Ok(_) => interp_ok(Scalar::from_i32(0)),
Err(e) => this.set_last_error_and_return_i32(e),
}
}
}
impl<'tcx> EvalContextPrivExt<'tcx> for crate::MiriInterpCx<'tcx> {}
@@ -1487,6 +1529,15 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// would be returned on UNIX-like systems. We thus remap this error to an EWOULDBLOCK.
interp_ok(Err(IoError::HostError(io::ErrorKind::WouldBlock.into())))
}
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),
}
}
+12 -5
View File
@@ -1,5 +1,6 @@
use rustc_abi::ExternAbi;
use crate::concurrency::thread::ThreadLookupError;
use crate::*;
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -51,9 +52,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
}
let thread = this.read_scalar(thread)?.to_int(this.libc_ty_layout("pthread_t").size)?;
let Ok(thread) = this.thread_id_try_from(thread) else {
this.write_scalar(this.eval_libc("ESRCH"), return_dest)?;
return interp_ok(());
// Joining a terminated thread is valid.
let thread = match this.thread_id_try_from(thread) {
Ok(id) | Err(ThreadLookupError::Terminated(id)) => id,
Err(ThreadLookupError::InvalidId) => {
this.write_scalar(this.eval_libc("ESRCH"), return_dest)?;
return interp_ok(());
}
};
this.join_thread_exclusive(
@@ -67,8 +72,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let this = self.eval_context_mut();
let thread = this.read_scalar(thread)?.to_int(this.libc_ty_layout("pthread_t").size)?;
let Ok(thread) = this.thread_id_try_from(thread) else {
return interp_ok(this.eval_libc("ESRCH"));
// Detaching a terminated thread is valid.
let thread = match this.thread_id_try_from(thread) {
Ok(id) | Err(ThreadLookupError::Terminated(id)) => id,
Err(ThreadLookupError::InvalidId) => return interp_ok(this.eval_libc("ESRCH")),
};
this.detach_thread(thread, /*allow_terminated_joined*/ false)?;
@@ -953,8 +953,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
Handle::Pseudo(PseudoHandle::CurrentThread) => this.active_thread(),
_ => this.invalid_handle("GetThreadDescription")?,
};
let tid = this.get_tid(thread);
this.write_scalar(Scalar::from_u32(tid), dest)?;
this.write_scalar(Scalar::from_u32(thread.to_u32()), dest)?;
}
"GetCurrentThreadId" => {
let [] = this.check_shim_sig(
@@ -963,9 +962,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
abi,
args,
)?;
let thread = this.active_thread();
let tid = this.get_tid(thread);
this.write_scalar(Scalar::from_u32(tid), dest)?;
this.write_scalar(Scalar::from_u32(this.active_thread().to_u32()), dest)?;
}
// Miscellaneous
+9 -6
View File
@@ -2,7 +2,6 @@ use std::mem::variant_count;
use rustc_abi::HasDataLayout;
use crate::concurrency::thread::ThreadNotFound;
use crate::shims::files::FdNum;
use crate::*;
@@ -45,7 +44,7 @@ impl PseudoHandle {
/// Errors that can occur when constructing a [`Handle`] from a Scalar.
pub enum HandleError {
/// There is no thread with the given ID.
ThreadNotFound(ThreadNotFound),
ThreadNotFound,
/// Can't convert scalar to handle because it is structurally invalid.
InvalidHandle,
}
@@ -185,10 +184,14 @@ impl Handle {
match Self::from_packed(handle) {
Some(Self::Thread(thread)) => {
// validate the thread id
// Validate the thread id. Windows handles remain valid even after thread
// termination.
use crate::concurrency::thread::ThreadLookupError;
match cx.machine.threads.thread_id_try_from(thread.to_u32()) {
Ok(id) => interp_ok(Ok(Self::Thread(id))),
Err(e) => interp_ok(Err(HandleError::ThreadNotFound(e))),
Ok(id) | Err(ThreadLookupError::Terminated(id)) =>
interp_ok(Ok(Self::Thread(id))),
Err(ThreadLookupError::InvalidId) =>
interp_ok(Err(HandleError::ThreadNotFound)),
}
}
Some(handle) => interp_ok(Ok(handle)),
@@ -214,7 +217,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
"invalid handle {} passed to {function_name}",
handle.to_target_isize(this)?,
))),
Err(HandleError::ThreadNotFound(_)) =>
Err(HandleError::ThreadNotFound) =>
throw_machine_stop!(TerminationInfo::Abort(format!(
"invalid thread ID {} passed to {function_name}",
handle.to_target_isize(this)?,
@@ -11,7 +11,6 @@ fn fill(v: &mut i32) {
fn evil() {
let _x = unsafe { *&mut *(LEAK as *mut i32) }; //~ ERROR: is a dangling pointer
}
fn main() {
@@ -1,11 +1,17 @@
//@only-target: linux # these are Linux-specific APIs
//@only-target: linux freebsd # these are Linux/FreeBSD-specific APIs
//@compile-flags: -Zmiri-disable-isolation -Zmiri-num-cpus=4
#![feature(io_error_more)]
#![feature(pointer_is_aligned_to)]
use std::mem::{size_of, size_of_val};
use libc::{cpu_set_t, sched_getaffinity, sched_setaffinity};
use libc::{sched_getaffinity, sched_setaffinity};
#[rustfmt::skip] // don't merge with imports above
#[cfg(any(target_os = "linux", target_os = "android"))]
use libc::cpu_set_t;
#[cfg(target_os = "freebsd")]
use libc::cpuset_t as cpu_set_t;
#[path = "../../utils/libc.rs"]
mod libc_utils;
@@ -195,6 +201,25 @@ fn parent_child() {
errno_check(unsafe { sched_setaffinity(PID, size_of::<cpu_set_t>(), &cpuset) });
}
#[cfg(any(target_os = "linux", target_os = "android"))]
fn use_gettid() {
// sched_getaffinity/sched_setaffinity also accept a TID (as returned by gettid)
let tid = unsafe { libc::gettid() };
assert_ne!(tid, 0);
let mut cpuset: cpu_set_t = unsafe { core::mem::MaybeUninit::zeroed().assume_init() };
let err = unsafe { sched_getaffinity(tid, std::mem::size_of::<cpu_set_t>(), &mut cpuset) };
assert_eq!(err, 0, "sched_getaffinity with gettid failed: {}", std::io::Error::last_os_error());
// An invalid TID returns ESRCH
let err = unsafe { sched_getaffinity(i32::MAX, std::mem::size_of::<cpu_set_t>(), &mut cpuset) };
assert_eq!(err, -1);
assert_eq!(std::io::Error::last_os_error().raw_os_error(), Some(libc::ESRCH));
// Setting affinity via TID also works
errno_check(unsafe { sched_setaffinity(tid, std::mem::size_of::<cpu_set_t>(), &cpuset) });
}
fn main() {
null_pointers();
configure_no_cpus();
@@ -204,4 +229,6 @@ fn main() {
set_small_cpu_mask();
set_custom_cpu_mask();
parent_child();
#[cfg(any(target_os = "linux", target_os = "android"))]
use_gettid();
}
@@ -45,6 +45,10 @@ fn main() {
test_getpeername_ipv4();
test_getpeername_ipv6();
test_shutdown();
test_shutdown_readable_after_write_close();
test_shutdown_writable_after_read_close();
}
/// Test creating a socket and then closing it afterwards.
@@ -504,3 +508,106 @@ fn test_getpeername_ipv6() {
server_thread.join().unwrap();
}
/// Test shutting down TCP streams.
fn test_shutdown() {
let (server_sockfd, addr) = net::make_listener_ipv4().unwrap();
let client_sockfd =
unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() };
// Spawn the server thread.
let server_thread = thread::spawn(move || net::accept_ipv4(server_sockfd).unwrap());
let mut byte = [0u8];
net::connect_ipv4(client_sockfd, addr).unwrap();
let client_dup_sockfd = unsafe { libc::dup(client_sockfd) };
// Closing should prevent reads/writes.
unsafe {
libc::shutdown(client_sockfd, libc::SHUT_RDWR);
let err = errno_result(libc::write(client_sockfd, [0u8].as_ptr().cast(), 1)).unwrap_err();
assert_eq!(err.kind(), ErrorKind::BrokenPipe);
let bytes_read =
errno_result(libc::read(client_sockfd, byte.as_mut_ptr().cast(), 1)).unwrap();
assert_eq!(bytes_read, 0);
}
// TODO: Once epoll is available for TCP sockets, ensure that the rdhup and hup readiness
// are set.
// Closing should affect previously duplicated handles.
unsafe {
let err =
errno_result(libc::write(client_dup_sockfd, [0u8].as_ptr().cast(), 1)).unwrap_err();
assert_eq!(err.kind(), ErrorKind::BrokenPipe);
let bytes_read =
errno_result(libc::read(client_dup_sockfd, byte.as_mut_ptr().cast(), 1)).unwrap();
assert_eq!(bytes_read, 0);
}
// Closing should affect newly duplicated handles.
unsafe {
let client_dup2_sockfd = libc::dup(client_sockfd);
let err =
errno_result(libc::write(client_dup2_sockfd, [0u8].as_ptr().cast(), 1)).unwrap_err();
assert_eq!(err.kind(), ErrorKind::BrokenPipe);
let bytes_read =
errno_result(libc::read(client_dup2_sockfd, byte.as_mut_ptr().cast(), 1)).unwrap();
assert_eq!(bytes_read, 0);
}
server_thread.join().unwrap();
}
/// Test that a socket is still readable after the write end has
/// been closed.
fn test_shutdown_readable_after_write_close() {
let (server_sockfd, addr) = net::make_listener_ipv4().unwrap();
let client_sockfd =
unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() };
// Spawn the server thread.
let server_thread = thread::spawn(move || {
let (peerfd, _) = net::accept_ipv4(server_sockfd).unwrap();
// Write a single byte which should be read later on.
unsafe { errno_result(libc::write(peerfd, [1u8].as_ptr().cast(), 1)).unwrap() };
});
net::connect_ipv4(client_sockfd, addr).unwrap();
unsafe {
// Close the write end.
libc::shutdown(client_sockfd, libc::SHUT_WR);
// Ensure that we're still readable.
let mut byte = [0u8];
errno_result(libc::read(client_sockfd, byte.as_mut_ptr().cast(), 1)).unwrap();
assert_eq!(&byte, &[1u8]);
}
server_thread.join().unwrap();
}
/// Test that a socket is still writable after the read end has
/// been closed.
fn test_shutdown_writable_after_read_close() {
let (server_sockfd, addr) = net::make_listener_ipv4().unwrap();
let client_sockfd =
unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() };
// Spawn the server thread.
let server_thread = thread::spawn(move || net::accept_ipv4(server_sockfd).unwrap());
net::connect_ipv4(client_sockfd, addr).unwrap();
unsafe {
// Close the read end.
libc::shutdown(client_sockfd, libc::SHUT_RD);
// Ensure that we're still writable.
errno_result(libc::write(client_sockfd, [1u8].as_ptr().cast(), 1)).unwrap();
}
server_thread.join().unwrap();
}
@@ -165,8 +165,13 @@ fn main() {
// The value is not important, we only care that whatever the value is,
// won't change from execution to execution.
if cfg!(with_isolation) {
if cfg!(any(target_os = "linux", target_os = "android")) {
// Linux starts the TID at the PID, which is 1000.
if cfg!(any(
target_os = "linux",
target_os = "android",
target_os = "freebsd",
target_os = "macos"
)) {
// On these OSes we start the TID at the PID, which is 1000.
assert_eq!(tid, 1000);
} else {
// Other platforms start counting from 0.
@@ -174,7 +179,7 @@ fn main() {
}
}
// On Linux, the first TID is the PID.
// On Linux, the main thread TID is the PID.
#[cfg(any(target_os = "linux", target_os = "android"))]
assert_eq!(tid, unsafe { libc::getpid() } as u64);
@@ -2,8 +2,11 @@
//! What makes it interesting as a test is that it relies on Stacked Borrow's "quirk"
//! in a fundamental, hard-to-fix-without-full-trees way.
//@revisions: stack tree
//@revisions: stack tree tree_implicit_writes
//@[tree]compile-flags: -Zmiri-tree-borrows
//@[tree_implicit_writes]compile-flags: -Zmiri-tree-borrows -Zmiri-tree-borrows-implicit-writes
#![feature(rustc_attrs)]
use std::marker::PhantomData;
use std::mem::{ManuallyDrop, MaybeUninit};
@@ -24,6 +27,7 @@ impl<T, const N: usize> RawSmallVec<T, N> {
Self { inline: ManuallyDrop::new(inline) }
}
#[rustc_no_writable]
const fn as_mut_ptr_inline(&mut self) -> *mut T {
&raw mut self.inline as *mut T
}
@@ -77,6 +81,7 @@ impl<T, const N: usize> SmallVec<T, N> {
size_of::<T>() == 0
}
#[rustc_no_writable]
pub const fn as_mut_ptr(&mut self) -> *mut T {
if self.len.on_heap(Self::is_zst()) {
// SAFETY: see above
@@ -1,4 +1,5 @@
//@revisions: stack tree
//@revisions: stack tree tree_implicit_writes
//@[tree_implicit_writes]compile-flags: -Zmiri-tree-borrows -Zmiri-tree-borrows-implicit-writes
//@[tree]compile-flags: -Zmiri-tree-borrows
#![feature(allocator_api)]
+2 -1
View File
@@ -1,4 +1,5 @@
//@revisions: stack tree
//@revisions: stack tree tree_implicit_writes
//@[tree_implicit_writes]compile-flags: -Zmiri-tree-borrows -Zmiri-tree-borrows-implicit-writes
//@[tree]compile-flags: -Zmiri-tree-borrows
use std::collections::HashMap;
use std::hash::BuildHasher;
+41 -2
View File
@@ -1,8 +1,8 @@
//@ignore-target: windows # No libc socket on Windows
//@compile-flags: -Zmiri-disable-isolation
use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
use std::io::{ErrorKind, Read, Write};
use std::net::{Shutdown, TcpListener, TcpStream};
use std::thread;
const TEST_BYTES: &[u8] = b"these are some test bytes!";
@@ -14,6 +14,7 @@ fn main() {
test_read_write();
test_peek();
test_peer_addr();
test_shutdown();
}
fn test_create_ipv4_listener() {
@@ -113,3 +114,41 @@ fn test_peer_addr() {
handle.join().unwrap();
}
/// Test shutting down TCP streams.
fn test_shutdown() {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
// Get local address with randomized port to know where
// we need to connect to.
let address = listener.local_addr().unwrap();
// Start server thread.
let handle = thread::spawn(move || {
let (stream, _addr) = listener.accept().unwrap();
// Return stream from thread such that it doesn't get dropped too early.
stream
});
let mut byte = [0u8];
let mut stream = TcpStream::connect(address).unwrap();
let mut stream_clone = stream.try_clone().unwrap();
// Closing should prevent reads/writes.
stream.shutdown(Shutdown::Write).unwrap();
stream.write(&[0]).unwrap_err();
stream.shutdown(Shutdown::Read).unwrap();
assert_eq!(stream.read(&mut byte).unwrap(), 0);
// Closing should affect previously cloned handles.
let err = stream_clone.write(&[0]).unwrap_err();
assert_eq!(err.kind(), ErrorKind::BrokenPipe);
assert_eq!(stream_clone.read(&mut byte).unwrap(), 0);
// Closing should affect newly cloned handles.
let mut stream_other_clone = stream.try_clone().unwrap();
let err = stream_other_clone.write(&[0]).unwrap_err();
assert_eq!(err.kind(), ErrorKind::BrokenPipe);
assert_eq!(stream_other_clone.read(&mut byte).unwrap(), 0);
let _stream = handle.join().unwrap();
}
+2 -1
View File
@@ -1,5 +1,6 @@
//@revisions: stack tree
//@revisions: stack tree tree_implicit_writes
//@[tree]compile-flags: -Zmiri-tree-borrows
//@[tree_implicit_writes]compile-flags: -Zmiri-tree-borrows -Zmiri-tree-borrows-implicit-writes
//@compile-flags: -Zmiri-strict-provenance
#![feature(slice_partition_dedup)]
#![feature(layout_for_ptr)]
@@ -1,4 +1,6 @@
//@compile-flags: -Zmiri-tree-borrows
//@revisions: tree tree_implicit_writes
//@[tree]compile-flags: -Zmiri-tree-borrows
//@[tree_implicit_writes]compile-flags: -Zmiri-tree-borrows -Zmiri-tree-borrows-implicit-writes
// copy_nonoverlapping works regardless of the order in which we construct
// the arguments.
@@ -0,0 +1,19 @@
// Tests that the `#[rustc_no_writable]` attribute disables implicit writes checking for the function it is applied to, so that `tests/fail/tree_borrows/implicit_writes/ptr_write.rs` would pass even with implicit writes enabled.
//@compile-flags: -Zmiri-tree-borrows -Zmiri-tree-borrows-implicit-writes
#![feature(rustc_attrs)]
fn main() {
let mut x = 0u8;
let ptr = &raw mut x;
let res = dereference(&mut x, ptr);
assert_eq!(*res, 0);
}
#[rustc_no_writable]
fn dereference<T>(x: T, y: *mut u8) -> T {
// miri inserts *no* implicit write here due to the attribute.
// Therefore we end up only reading from `x` and `y` which is fine.
let _ = unsafe { *y };
x
}
+2 -1
View File
@@ -1,5 +1,6 @@
//@revisions: stack tree
//@revisions: stack tree tree_implicit_writes
//@compile-flags: -Zmiri-strict-provenance
//@[tree_implicit_writes]compile-flags: -Zmiri-tree-borrows -Zmiri-tree-borrows-implicit-writes
//@[tree]compile-flags: -Zmiri-tree-borrows
#![feature(iter_advance_by, iter_next_chunk)]
@@ -0,0 +1,5 @@
thread 'main' ($TID) panicked at tests/pass/vec.rs:LL:CC:
explicit panic
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
note: in Miri, you may have to set `MIRIFLAGS=-Zmiri-env-forward=RUST_BACKTRACE` for the environment variable to have an effect