mirror of
https://github.com/rust-lang/rust.git
synced 2026-05-06 08:36:52 -04:00
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:
+6
-6
@@ -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
@@ -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 @@
|
||||
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);
|
||||
|
||||
@@ -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"
|
||||
);
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
@@ -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)?;
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)]
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user