mirror of
https://github.com/rust-lang/rust.git
synced 2025-05-04 14:07:36 +00:00
Auto merge of #135742 - RalfJung:miri-sync, r=RalfJung
Miri subtree update r? `@ghost`
This commit is contained in:
commit
9a1d156f38
2
src/tools/miri/.github/workflows/ci.yml
vendored
2
src/tools/miri/.github/workflows/ci.yml
vendored
@ -126,7 +126,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 256 # get a bit more of the history
|
||||
- name: install josh-proxy
|
||||
run: RUSTFLAGS="--cap-lints warn" cargo +stable install josh-proxy --git https://github.com/josh-project/josh --tag r23.12.04
|
||||
run: cargo +stable install josh-proxy --git https://github.com/josh-project/josh --tag r24.10.04
|
||||
- name: setup bot git name and email
|
||||
run: |
|
||||
git config --global user.name 'The Miri Cronjob Bot'
|
||||
|
@ -290,7 +290,7 @@ We use the [`josh` proxy](https://github.com/josh-project/josh) to transmit chan
|
||||
rustc and Miri repositories. You can install it as follows:
|
||||
|
||||
```sh
|
||||
RUSTFLAGS="--cap-lints=warn" cargo +stable install josh-proxy --git https://github.com/josh-project/josh --tag r23.12.04
|
||||
cargo +stable install josh-proxy --git https://github.com/josh-project/josh --tag r24.10.04
|
||||
```
|
||||
|
||||
Josh will automatically be started and stopped by `./miri`.
|
||||
|
@ -217,8 +217,8 @@ degree documented below):
|
||||
- For every other target with OS `linux`, `macos`, or `windows`, Miri should generally work, but we
|
||||
make no promises and we don't run tests for such targets.
|
||||
- We have unofficial support (not maintained by the Miri team itself) for some further operating systems.
|
||||
- `solaris` / `illumos`: maintained by @devnexen. Supports `std::{env, thread, sync}`, but not `std::fs`.
|
||||
- `freebsd`: **maintainer wanted**. Supports `std::env` and parts of `std::{thread, fs}`, but not `std::sync`.
|
||||
- `solaris` / `illumos`: maintained by @devnexen. Supports the entire test suite.
|
||||
- `freebsd`: maintained by @YohDeadfall. Supports `std::env` and parts of `std::{thread, fs}`, but not `std::sync`.
|
||||
- `android`: **maintainer wanted**. Support very incomplete, but a basic "hello world" works.
|
||||
- `wasi`: **maintainer wanted**. Support very incomplete, not even standard output works, but an empty `main` function works.
|
||||
- For targets on other operating systems, Miri might fail before even reaching the `main` function.
|
||||
|
@ -147,13 +147,14 @@ case $HOST_TARGET in
|
||||
# Extra tier 2
|
||||
TEST_TARGET=arm-unknown-linux-gnueabi run_tests
|
||||
TEST_TARGET=s390x-unknown-linux-gnu run_tests # big-endian architecture of choice
|
||||
# Not officially supported tier 2
|
||||
TEST_TARGET=x86_64-unknown-illumos run_tests
|
||||
TEST_TARGET=x86_64-pc-solaris run_tests
|
||||
# Partially supported targets (tier 2)
|
||||
BASIC="empty_main integer heap_alloc libc-mem vec string btreemap" # ensures we have the basics: pre-main code, system allocator
|
||||
UNIX="hello panic/panic panic/unwind concurrency/simple atomic libc-mem libc-misc libc-random env num_cpus" # the things that are very similar across all Unixes, and hence easily supported there
|
||||
TEST_TARGET=x86_64-unknown-freebsd run_tests_minimal $BASIC $UNIX time hashmap random threadname pthread fs libc-pipe
|
||||
TEST_TARGET=i686-unknown-freebsd run_tests_minimal $BASIC $UNIX time hashmap random threadname pthread fs libc-pipe
|
||||
TEST_TARGET=x86_64-unknown-illumos run_tests_minimal $BASIC $UNIX time hashmap random thread sync available-parallelism tls libc-pipe fs
|
||||
TEST_TARGET=x86_64-pc-solaris run_tests_minimal $BASIC $UNIX time hashmap random thread sync available-parallelism tls libc-pipe fs
|
||||
TEST_TARGET=aarch64-linux-android run_tests_minimal $BASIC $UNIX time hashmap random sync threadname pthread epoll eventfd
|
||||
TEST_TARGET=wasm32-wasip2 run_tests_minimal $BASIC wasm
|
||||
TEST_TARGET=wasm32-unknown-unknown run_tests_minimal no_std empty_main wasm # this target doesn't really have std
|
||||
|
@ -5,21 +5,19 @@
|
||||
"cargo-miri/Cargo.toml",
|
||||
"miri-script/Cargo.toml",
|
||||
],
|
||||
"rust-analyzer.check.invocationLocation": "root",
|
||||
"rust-analyzer.check.invocationStrategy": "once",
|
||||
"rust-analyzer.check.overrideCommand": [
|
||||
"env",
|
||||
"MIRI_AUTO_OPS=no",
|
||||
"./miri",
|
||||
"clippy", // make this `check` when working with a locally built rustc
|
||||
"--message-format=json",
|
||||
],
|
||||
"rust-analyzer.cargo.extraEnv": {
|
||||
"MIRI_AUTO_OPS": "no",
|
||||
"MIRI_IN_RA": "1",
|
||||
},
|
||||
// Contrary to what the name suggests, this also affects proc macros.
|
||||
"rust-analyzer.cargo.buildScripts.invocationLocation": "root",
|
||||
"rust-analyzer.cargo.buildScripts.invocationStrategy": "once",
|
||||
"rust-analyzer.cargo.buildScripts.overrideCommand": [
|
||||
"env",
|
||||
"MIRI_AUTO_OPS=no",
|
||||
"./miri",
|
||||
"check",
|
||||
"--message-format=json",
|
||||
|
@ -3,12 +3,16 @@ set -e
|
||||
# We want to call the binary directly, so we need to know where it ends up.
|
||||
ROOT_DIR="$(dirname "$0")"
|
||||
MIRI_SCRIPT_TARGET_DIR="$ROOT_DIR"/miri-script/target
|
||||
# If stdout is not a terminal and we are not on CI, assume that we are being invoked by RA, and use JSON output.
|
||||
if ! [ -t 1 ] && [ -z "$CI" ]; then
|
||||
TOOLCHAIN="+nightly"
|
||||
# If we are being invoked for RA, use JSON output and the default toolchain (to make proc-macros
|
||||
# work in RA). This needs a different target dir to avoid mixing up the builds.
|
||||
if [ -n "$MIRI_IN_RA" ]; then
|
||||
MESSAGE_FORMAT="--message-format=json"
|
||||
TOOLCHAIN=""
|
||||
MIRI_SCRIPT_TARGET_DIR="$MIRI_SCRIPT_TARGET_DIR"/ra
|
||||
fi
|
||||
# We need a nightly toolchain, for `-Zroot-dir`.
|
||||
cargo +nightly build $CARGO_EXTRA_FLAGS --manifest-path "$ROOT_DIR"/miri-script/Cargo.toml \
|
||||
cargo $TOOLCHAIN build $CARGO_EXTRA_FLAGS --manifest-path "$ROOT_DIR"/miri-script/Cargo.toml \
|
||||
-Zroot-dir="$ROOT_DIR" \
|
||||
-q --target-dir "$MIRI_SCRIPT_TARGET_DIR" $MESSAGE_FORMAT || \
|
||||
( echo "Failed to build miri-script. Is the 'nightly' toolchain installed?"; exit 1 )
|
||||
|
@ -423,7 +423,7 @@ impl Command {
|
||||
.map(|path| path.into_os_string().into_string().unwrap())
|
||||
.collect()
|
||||
} else {
|
||||
benches.into_iter().map(Into::into).collect()
|
||||
benches.into_iter().collect()
|
||||
};
|
||||
let target_flag = if let Some(target) = target {
|
||||
let mut flag = OsString::from("--target=");
|
||||
@ -564,6 +564,10 @@ impl Command {
|
||||
if bless {
|
||||
e.sh.set_var("RUSTC_BLESS", "Gesundheit");
|
||||
}
|
||||
if e.sh.var("MIRI_TEST_TARGET").is_ok() {
|
||||
// Avoid trouble due to an incorrectly set env var.
|
||||
bail!("MIRI_TEST_TARGET must not be set when invoking `./miri test`");
|
||||
}
|
||||
if let Some(target) = target {
|
||||
// Tell the harness which target to test.
|
||||
e.sh.set_var("MIRI_TEST_TARGET", target);
|
||||
|
@ -111,6 +111,7 @@ pub enum Command {
|
||||
/// `rustup-toolchain-install-master` must be installed for this to work.
|
||||
Toolchain {
|
||||
/// Flags that are passed through to `rustup-toolchain-install-master`.
|
||||
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
|
||||
flags: Vec<String>,
|
||||
},
|
||||
/// Pull and merge Miri changes from the rustc repo.
|
||||
|
@ -1 +1 @@
|
||||
13170cd787cb733ed24842ee825bcbd98dc01476
|
||||
01706e1a34c87656fcbfce198608f4cd2ac6461a
|
||||
|
@ -54,8 +54,8 @@ impl CpuAffinityMask {
|
||||
let chunk = self.0[start..].first_chunk_mut::<4>().unwrap();
|
||||
let offset = cpu % 32;
|
||||
*chunk = match target.options.endian {
|
||||
Endian::Little => (u32::from_le_bytes(*chunk) | 1 << offset).to_le_bytes(),
|
||||
Endian::Big => (u32::from_be_bytes(*chunk) | 1 << offset).to_be_bytes(),
|
||||
Endian::Little => (u32::from_le_bytes(*chunk) | (1 << offset)).to_le_bytes(),
|
||||
Endian::Big => (u32::from_be_bytes(*chunk) | (1 << offset)).to_be_bytes(),
|
||||
};
|
||||
}
|
||||
8 => {
|
||||
@ -63,8 +63,8 @@ impl CpuAffinityMask {
|
||||
let chunk = self.0[start..].first_chunk_mut::<8>().unwrap();
|
||||
let offset = cpu % 64;
|
||||
*chunk = match target.options.endian {
|
||||
Endian::Little => (u64::from_le_bytes(*chunk) | 1 << offset).to_le_bytes(),
|
||||
Endian::Big => (u64::from_be_bytes(*chunk) | 1 << offset).to_be_bytes(),
|
||||
Endian::Little => (u64::from_le_bytes(*chunk) | (1 << offset)).to_le_bytes(),
|
||||
Endian::Big => (u64::from_be_bytes(*chunk) | (1 << offset)).to_be_bytes(),
|
||||
};
|
||||
}
|
||||
other => bug!("chunk size not supported: {other}"),
|
||||
|
@ -422,7 +422,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
mutex_ref: MutexRef,
|
||||
retval_dest: Option<(Scalar, MPlaceTy<'tcx>)>,
|
||||
}
|
||||
@unblock = |this| {
|
||||
|this, unblock: UnblockKind| {
|
||||
assert_eq!(unblock, UnblockKind::Ready);
|
||||
|
||||
assert!(!this.mutex_is_locked(&mutex_ref));
|
||||
this.mutex_lock(&mutex_ref);
|
||||
|
||||
@ -538,7 +540,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
retval: Scalar,
|
||||
dest: MPlaceTy<'tcx>,
|
||||
}
|
||||
@unblock = |this| {
|
||||
|this, unblock: UnblockKind| {
|
||||
assert_eq!(unblock, UnblockKind::Ready);
|
||||
this.rwlock_reader_lock(id);
|
||||
this.write_scalar(retval, &dest)?;
|
||||
interp_ok(())
|
||||
@ -623,7 +626,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
retval: Scalar,
|
||||
dest: MPlaceTy<'tcx>,
|
||||
}
|
||||
@unblock = |this| {
|
||||
|this, unblock: UnblockKind| {
|
||||
assert_eq!(unblock, UnblockKind::Ready);
|
||||
this.rwlock_writer_lock(id);
|
||||
this.write_scalar(retval, &dest)?;
|
||||
interp_ok(())
|
||||
@ -677,25 +681,29 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
retval_timeout: Scalar,
|
||||
dest: MPlaceTy<'tcx>,
|
||||
}
|
||||
@unblock = |this| {
|
||||
// The condvar was signaled. Make sure we get the clock for that.
|
||||
if let Some(data_race) = &this.machine.data_race {
|
||||
data_race.acquire_clock(
|
||||
&this.machine.sync.condvars[condvar].clock,
|
||||
&this.machine.threads,
|
||||
);
|
||||
|this, unblock: UnblockKind| {
|
||||
match unblock {
|
||||
UnblockKind::Ready => {
|
||||
// The condvar was signaled. Make sure we get the clock for that.
|
||||
if let Some(data_race) = &this.machine.data_race {
|
||||
data_race.acquire_clock(
|
||||
&this.machine.sync.condvars[condvar].clock,
|
||||
&this.machine.threads,
|
||||
);
|
||||
}
|
||||
// Try to acquire the mutex.
|
||||
// The timeout only applies to the first wait (until the signal), not for mutex acquisition.
|
||||
this.condvar_reacquire_mutex(&mutex_ref, retval_succ, dest)
|
||||
}
|
||||
UnblockKind::TimedOut => {
|
||||
// We have to remove the waiter from the queue again.
|
||||
let thread = this.active_thread();
|
||||
let waiters = &mut this.machine.sync.condvars[condvar].waiters;
|
||||
waiters.retain(|waiter| *waiter != thread);
|
||||
// Now get back the lock.
|
||||
this.condvar_reacquire_mutex(&mutex_ref, retval_timeout, dest)
|
||||
}
|
||||
}
|
||||
// Try to acquire the mutex.
|
||||
// The timeout only applies to the first wait (until the signal), not for mutex acquisition.
|
||||
this.condvar_reacquire_mutex(&mutex_ref, retval_succ, dest)
|
||||
}
|
||||
@timeout = |this| {
|
||||
// We have to remove the waiter from the queue again.
|
||||
let thread = this.active_thread();
|
||||
let waiters = &mut this.machine.sync.condvars[condvar].waiters;
|
||||
waiters.retain(|waiter| *waiter != thread);
|
||||
// Now get back the lock.
|
||||
this.condvar_reacquire_mutex(&mutex_ref, retval_timeout, dest)
|
||||
}
|
||||
),
|
||||
);
|
||||
@ -752,25 +760,29 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
dest: MPlaceTy<'tcx>,
|
||||
errno_timeout: IoError,
|
||||
}
|
||||
@unblock = |this| {
|
||||
let futex = futex_ref.0.borrow();
|
||||
// Acquire the clock of the futex.
|
||||
if let Some(data_race) = &this.machine.data_race {
|
||||
data_race.acquire_clock(&futex.clock, &this.machine.threads);
|
||||
|this, unblock: UnblockKind| {
|
||||
match unblock {
|
||||
UnblockKind::Ready => {
|
||||
let futex = futex_ref.0.borrow();
|
||||
// Acquire the clock of the futex.
|
||||
if let Some(data_race) = &this.machine.data_race {
|
||||
data_race.acquire_clock(&futex.clock, &this.machine.threads);
|
||||
}
|
||||
// Write the return value.
|
||||
this.write_scalar(retval_succ, &dest)?;
|
||||
interp_ok(())
|
||||
},
|
||||
UnblockKind::TimedOut => {
|
||||
// Remove the waiter from the futex.
|
||||
let thread = this.active_thread();
|
||||
let mut futex = futex_ref.0.borrow_mut();
|
||||
futex.waiters.retain(|waiter| waiter.thread != thread);
|
||||
// Set errno and write return value.
|
||||
this.set_last_error(errno_timeout)?;
|
||||
this.write_scalar(retval_timeout, &dest)?;
|
||||
interp_ok(())
|
||||
},
|
||||
}
|
||||
// Write the return value.
|
||||
this.write_scalar(retval_succ, &dest)?;
|
||||
interp_ok(())
|
||||
}
|
||||
@timeout = |this| {
|
||||
// Remove the waiter from the futex.
|
||||
let thread = this.active_thread();
|
||||
let mut futex = futex_ref.0.borrow_mut();
|
||||
futex.waiters.retain(|waiter| waiter.thread != thread);
|
||||
// Set errno and write return value.
|
||||
this.set_last_error(errno_timeout)?;
|
||||
this.write_scalar(retval_timeout, &dest)?;
|
||||
interp_ok(())
|
||||
}
|
||||
),
|
||||
);
|
||||
|
@ -19,7 +19,7 @@ use crate::concurrency::data_race;
|
||||
use crate::shims::tls;
|
||||
use crate::*;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
enum SchedulingAction {
|
||||
/// Execute step on the active thread.
|
||||
ExecuteStep,
|
||||
@ -30,6 +30,7 @@ enum SchedulingAction {
|
||||
}
|
||||
|
||||
/// What to do with TLS allocations from terminated threads
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum TlsAllocAction {
|
||||
/// Deallocate backing memory of thread-local statics as usual
|
||||
Deallocate,
|
||||
@ -38,71 +39,18 @@ pub enum TlsAllocAction {
|
||||
Leak,
|
||||
}
|
||||
|
||||
/// Trait for callbacks that are executed when a thread gets unblocked.
|
||||
pub trait UnblockCallback<'tcx>: VisitProvenance {
|
||||
/// Will be invoked when the thread was unblocked the "regular" way,
|
||||
/// i.e. whatever event it was blocking on has happened.
|
||||
fn unblock(self: Box<Self>, ecx: &mut InterpCx<'tcx, MiriMachine<'tcx>>) -> InterpResult<'tcx>;
|
||||
|
||||
/// Will be invoked when the timeout ellapsed without the event the
|
||||
/// thread was blocking on having occurred.
|
||||
fn timeout(self: Box<Self>, _ecx: &mut InterpCx<'tcx, MiriMachine<'tcx>>)
|
||||
-> InterpResult<'tcx>;
|
||||
/// The argument type for the "unblock" callback, indicating why the thread got unblocked.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum UnblockKind {
|
||||
/// Operation completed successfully, thread continues normal execution.
|
||||
Ready,
|
||||
/// The operation did not complete within its specified duration.
|
||||
TimedOut,
|
||||
}
|
||||
pub type DynUnblockCallback<'tcx> = Box<dyn UnblockCallback<'tcx> + 'tcx>;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! callback {
|
||||
(
|
||||
@capture<$tcx:lifetime $(,)? $($lft:lifetime),*> { $($name:ident: $type:ty),* $(,)? }
|
||||
@unblock = |$this:ident| $unblock:block
|
||||
) => {
|
||||
callback!(
|
||||
@capture<$tcx, $($lft),*> { $($name: $type),* }
|
||||
@unblock = |$this| $unblock
|
||||
@timeout = |_this| {
|
||||
unreachable!(
|
||||
"timeout on a thread that was blocked without a timeout (or someone forgot to overwrite this method)"
|
||||
)
|
||||
}
|
||||
)
|
||||
};
|
||||
(
|
||||
@capture<$tcx:lifetime $(,)? $($lft:lifetime),*> { $($name:ident: $type:ty),* $(,)? }
|
||||
@unblock = |$this:ident| $unblock:block
|
||||
@timeout = |$this_timeout:ident| $timeout:block
|
||||
) => {{
|
||||
struct Callback<$tcx, $($lft),*> {
|
||||
$($name: $type,)*
|
||||
_phantom: std::marker::PhantomData<&$tcx ()>,
|
||||
}
|
||||
|
||||
impl<$tcx, $($lft),*> VisitProvenance for Callback<$tcx, $($lft),*> {
|
||||
#[allow(unused_variables)]
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
$(
|
||||
self.$name.visit_provenance(visit);
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
impl<$tcx, $($lft),*> UnblockCallback<$tcx> for Callback<$tcx, $($lft),*> {
|
||||
fn unblock(self: Box<Self>, $this: &mut MiriInterpCx<$tcx>) -> InterpResult<$tcx> {
|
||||
#[allow(unused_variables)]
|
||||
let Callback { $($name,)* _phantom } = *self;
|
||||
$unblock
|
||||
}
|
||||
|
||||
fn timeout(self: Box<Self>, $this_timeout: &mut MiriInterpCx<$tcx>) -> InterpResult<$tcx> {
|
||||
#[allow(unused_variables)]
|
||||
let Callback { $($name,)* _phantom } = *self;
|
||||
$timeout
|
||||
}
|
||||
}
|
||||
|
||||
Box::new(Callback { $($name,)* _phantom: std::marker::PhantomData })
|
||||
}}
|
||||
}
|
||||
/// Type alias for unblock callbacks, i.e. machine callbacks invoked when
|
||||
/// a thread gets unblocked.
|
||||
pub type DynUnblockCallback<'tcx> = DynMachineCallback<'tcx, UnblockKind>;
|
||||
|
||||
/// A thread identifier.
|
||||
#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)]
|
||||
@ -656,7 +604,8 @@ impl<'tcx> ThreadManager<'tcx> {
|
||||
@capture<'tcx> {
|
||||
joined_thread_id: ThreadId,
|
||||
}
|
||||
@unblock = |this| {
|
||||
|this, unblock: UnblockKind| {
|
||||
assert_eq!(unblock, UnblockKind::Ready);
|
||||
if let Some(data_race) = &mut this.machine.data_race {
|
||||
data_race.thread_joined(&this.machine.threads, joined_thread_id);
|
||||
}
|
||||
@ -842,7 +791,7 @@ trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> {
|
||||
// 2. Make the scheduler the only place that can change the active
|
||||
// thread.
|
||||
let old_thread = this.machine.threads.set_active_thread_id(thread);
|
||||
callback.timeout(this)?;
|
||||
callback.call(this, UnblockKind::TimedOut)?;
|
||||
this.machine.threads.set_active_thread_id(old_thread);
|
||||
}
|
||||
// found_callback can remain None if the computer's clock
|
||||
@ -1084,7 +1033,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
};
|
||||
// The callback must be executed in the previously blocked thread.
|
||||
let old_thread = this.machine.threads.set_active_thread_id(thread);
|
||||
callback.unblock(this)?;
|
||||
callback.call(this, UnblockKind::Ready)?;
|
||||
this.machine.threads.set_active_thread_id(old_thread);
|
||||
interp_ok(())
|
||||
}
|
||||
|
@ -262,6 +262,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Helper function to get a `libc` constant as an `u64`.
|
||||
fn eval_libc_u64(&self, name: &str) -> u64 {
|
||||
// TODO: Cache the result.
|
||||
self.eval_libc(name).to_u64().unwrap_or_else(|_err| {
|
||||
panic!("required libc item has unexpected type (not `u64`): {name}")
|
||||
})
|
||||
}
|
||||
|
||||
/// Helper function to get a `windows` constant as a `Scalar`.
|
||||
fn eval_windows(&self, module: &str, name: &str) -> Scalar {
|
||||
self.eval_context_ref().eval_path_scalar(&["std", "sys", "pal", "windows", module, name])
|
||||
|
@ -13,6 +13,8 @@
|
||||
#![feature(strict_overflow_ops)]
|
||||
#![feature(pointer_is_aligned_to)]
|
||||
#![feature(unqualified_local_imports)]
|
||||
#![feature(derive_coerce_pointee)]
|
||||
#![feature(arbitrary_self_types)]
|
||||
// Configure clippy and other lints
|
||||
#![allow(
|
||||
clippy::collapsible_else_if,
|
||||
@ -126,8 +128,8 @@ pub use crate::concurrency::sync::{
|
||||
CondvarId, EvalContextExt as _, MutexRef, RwLockId, SynchronizationObjects,
|
||||
};
|
||||
pub use crate::concurrency::thread::{
|
||||
BlockReason, EvalContextExt as _, StackEmptyCallback, ThreadId, ThreadManager, TimeoutAnchor,
|
||||
TimeoutClock, UnblockCallback,
|
||||
BlockReason, DynUnblockCallback, EvalContextExt as _, StackEmptyCallback, ThreadId,
|
||||
ThreadManager, TimeoutAnchor, TimeoutClock, UnblockKind,
|
||||
};
|
||||
pub use crate::diagnostics::{
|
||||
EvalContextExt as _, NonHaltingDiagnostic, TerminationInfo, report_error,
|
||||
@ -139,8 +141,8 @@ pub use crate::eval::{
|
||||
pub use crate::helpers::{AccessKind, EvalContextExt as _};
|
||||
pub use crate::intrinsics::EvalContextExt as _;
|
||||
pub use crate::machine::{
|
||||
AllocExtra, FrameExtra, MemoryKind, MiriInterpCx, MiriInterpCxExt, MiriMachine, MiriMemoryKind,
|
||||
PrimitiveLayouts, Provenance, ProvenanceExtra,
|
||||
AllocExtra, DynMachineCallback, FrameExtra, MachineCallback, MemoryKind, MiriInterpCx,
|
||||
MiriInterpCxExt, MiriMachine, MiriMemoryKind, PrimitiveLayouts, Provenance, ProvenanceExtra,
|
||||
};
|
||||
pub use crate::mono_hash_map::MonoHashMap;
|
||||
pub use crate::operator::EvalContextExt as _;
|
||||
|
@ -1723,3 +1723,69 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
|
||||
Cow::Borrowed(ecx.machine.union_data_ranges.entry(ty).or_insert_with(compute_range))
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for callbacks handling asynchronous machine operations.
|
||||
pub trait MachineCallback<'tcx, T>: VisitProvenance {
|
||||
/// The function to be invoked when the callback is fired.
|
||||
fn call(
|
||||
self: Box<Self>,
|
||||
ecx: &mut InterpCx<'tcx, MiriMachine<'tcx>>,
|
||||
arg: T,
|
||||
) -> InterpResult<'tcx>;
|
||||
}
|
||||
|
||||
/// Type alias for boxed machine callbacks with generic argument type.
|
||||
pub type DynMachineCallback<'tcx, T> = Box<dyn MachineCallback<'tcx, T> + 'tcx>;
|
||||
|
||||
/// Creates a `DynMachineCallback`:
|
||||
///
|
||||
/// ```rust
|
||||
/// callback!(
|
||||
/// @capture<'tcx> {
|
||||
/// var1: Ty1,
|
||||
/// var2: Ty2<'tcx>,
|
||||
/// }
|
||||
/// |this, arg: ArgTy| {
|
||||
/// // Implement the callback here.
|
||||
/// todo!()
|
||||
/// }
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// All the argument types must implement `VisitProvenance`.
|
||||
#[macro_export]
|
||||
macro_rules! callback {
|
||||
(@capture<$tcx:lifetime $(,)? $($lft:lifetime),*>
|
||||
{ $($name:ident: $type:ty),* $(,)? }
|
||||
|$this:ident, $arg:ident: $arg_ty:ty| $body:expr $(,)?) => {{
|
||||
struct Callback<$tcx, $($lft),*> {
|
||||
$($name: $type,)*
|
||||
_phantom: std::marker::PhantomData<&$tcx ()>,
|
||||
}
|
||||
|
||||
impl<$tcx, $($lft),*> VisitProvenance for Callback<$tcx, $($lft),*> {
|
||||
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
|
||||
$(
|
||||
self.$name.visit_provenance(_visit);
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
impl<$tcx, $($lft),*> MachineCallback<$tcx, $arg_ty> for Callback<$tcx, $($lft),*> {
|
||||
fn call(
|
||||
self: Box<Self>,
|
||||
$this: &mut MiriInterpCx<$tcx>,
|
||||
$arg: $arg_ty
|
||||
) -> InterpResult<$tcx> {
|
||||
#[allow(unused_variables)]
|
||||
let Callback { $($name,)* _phantom } = *self;
|
||||
$body
|
||||
}
|
||||
}
|
||||
|
||||
Box::new(Callback {
|
||||
$($name,)*
|
||||
_phantom: std::marker::PhantomData
|
||||
})
|
||||
}};
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
use std::any::Any;
|
||||
use std::collections::BTreeMap;
|
||||
use std::io::{IsTerminal, Read, SeekFrom, Write};
|
||||
use std::marker::CoercePointee;
|
||||
use std::ops::Deref;
|
||||
use std::rc::{Rc, Weak};
|
||||
use std::{fs, io};
|
||||
@ -10,16 +11,132 @@ use rustc_abi::Size;
|
||||
use crate::shims::unix::UnixFileDescription;
|
||||
use crate::*;
|
||||
|
||||
/// A unique id for file descriptions. While we could use the address, considering that
|
||||
/// is definitely unique, the address would expose interpreter internal state when used
|
||||
/// for sorting things. So instead we generate a unique id per file description is the name
|
||||
/// for all `dup`licates and is never reused.
|
||||
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub struct FdId(usize);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct FdIdWith<T: ?Sized> {
|
||||
id: FdId,
|
||||
inner: T,
|
||||
}
|
||||
|
||||
/// A refcounted pointer to a file description, also tracking the
|
||||
/// globally unique ID of this file description.
|
||||
#[repr(transparent)]
|
||||
#[derive(CoercePointee, Debug)]
|
||||
pub struct FileDescriptionRef<T: ?Sized>(Rc<FdIdWith<T>>);
|
||||
|
||||
impl<T: ?Sized> Clone for FileDescriptionRef<T> {
|
||||
fn clone(&self) -> Self {
|
||||
FileDescriptionRef(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Deref for FileDescriptionRef<T> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &T {
|
||||
&self.0.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> FileDescriptionRef<T> {
|
||||
pub fn id(&self) -> FdId {
|
||||
self.0.id
|
||||
}
|
||||
}
|
||||
|
||||
/// Holds a weak reference to the actual file description.
|
||||
#[derive(Debug)]
|
||||
pub struct WeakFileDescriptionRef<T: ?Sized>(Weak<FdIdWith<T>>);
|
||||
|
||||
impl<T: ?Sized> Clone for WeakFileDescriptionRef<T> {
|
||||
fn clone(&self) -> Self {
|
||||
WeakFileDescriptionRef(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> FileDescriptionRef<T> {
|
||||
pub fn downgrade(this: &Self) -> WeakFileDescriptionRef<T> {
|
||||
WeakFileDescriptionRef(Rc::downgrade(&this.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> WeakFileDescriptionRef<T> {
|
||||
pub fn upgrade(&self) -> Option<FileDescriptionRef<T>> {
|
||||
self.0.upgrade().map(FileDescriptionRef)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> VisitProvenance for WeakFileDescriptionRef<T> {
|
||||
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
|
||||
// A weak reference can never be the only reference to some pointer or place.
|
||||
// Since the actual file description is tracked by strong ref somewhere,
|
||||
// it is ok to make this a NOP operation.
|
||||
}
|
||||
}
|
||||
|
||||
/// A helper trait to indirectly allow downcasting on `Rc<FdIdWith<dyn _>>`.
|
||||
/// Ideally we'd just add a `FdIdWith<Self>: Any` bound to the `FileDescription` trait,
|
||||
/// but that does not allow upcasting.
|
||||
pub trait FileDescriptionExt: 'static {
|
||||
fn into_rc_any(self: FileDescriptionRef<Self>) -> Rc<dyn Any>;
|
||||
|
||||
/// We wrap the regular `close` function generically, so both handle `Rc::into_inner`
|
||||
/// and epoll interest management.
|
||||
fn close_ref<'tcx>(
|
||||
self: FileDescriptionRef<Self>,
|
||||
communicate_allowed: bool,
|
||||
ecx: &mut MiriInterpCx<'tcx>,
|
||||
) -> InterpResult<'tcx, io::Result<()>>;
|
||||
}
|
||||
|
||||
impl<T: FileDescription + 'static> FileDescriptionExt for T {
|
||||
fn into_rc_any(self: FileDescriptionRef<Self>) -> Rc<dyn Any> {
|
||||
self.0
|
||||
}
|
||||
|
||||
fn close_ref<'tcx>(
|
||||
self: FileDescriptionRef<Self>,
|
||||
communicate_allowed: bool,
|
||||
ecx: &mut MiriInterpCx<'tcx>,
|
||||
) -> InterpResult<'tcx, io::Result<()>> {
|
||||
match Rc::into_inner(self.0) {
|
||||
Some(fd) => {
|
||||
// Remove entry from the global epoll_event_interest table.
|
||||
ecx.machine.epoll_interests.remove(fd.id);
|
||||
|
||||
fd.inner.close(communicate_allowed, ecx)
|
||||
}
|
||||
None => {
|
||||
// Not the last reference.
|
||||
interp_ok(Ok(()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type DynFileDescriptionRef = FileDescriptionRef<dyn FileDescription>;
|
||||
|
||||
impl FileDescriptionRef<dyn FileDescription> {
|
||||
pub fn downcast<T: FileDescription + 'static>(self) -> Option<FileDescriptionRef<T>> {
|
||||
let inner = self.into_rc_any().downcast::<FdIdWith<T>>().ok()?;
|
||||
Some(FileDescriptionRef(inner))
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents an open file description.
|
||||
pub trait FileDescription: std::fmt::Debug + Any {
|
||||
pub trait FileDescription: std::fmt::Debug + FileDescriptionExt {
|
||||
fn name(&self) -> &'static str;
|
||||
|
||||
/// Reads as much as possible into the given buffer `ptr`.
|
||||
/// `len` indicates how many bytes we should try to read.
|
||||
/// `dest` is where the return value should be stored: number of bytes read, or `-1` in case of error.
|
||||
fn read<'tcx>(
|
||||
&self,
|
||||
_self_ref: &FileDescriptionRef,
|
||||
self: FileDescriptionRef<Self>,
|
||||
_communicate_allowed: bool,
|
||||
_ptr: Pointer,
|
||||
_len: usize,
|
||||
@ -33,8 +150,7 @@ pub trait FileDescription: std::fmt::Debug + Any {
|
||||
/// `len` indicates how many bytes we should try to write.
|
||||
/// `dest` is where the return value should be stored: number of bytes written, or `-1` in case of error.
|
||||
fn write<'tcx>(
|
||||
&self,
|
||||
_self_ref: &FileDescriptionRef,
|
||||
self: FileDescriptionRef<Self>,
|
||||
_communicate_allowed: bool,
|
||||
_ptr: Pointer,
|
||||
_len: usize,
|
||||
@ -54,11 +170,15 @@ pub trait FileDescription: std::fmt::Debug + Any {
|
||||
throw_unsup_format!("cannot seek on {}", self.name());
|
||||
}
|
||||
|
||||
/// Close the file descriptor.
|
||||
fn close<'tcx>(
|
||||
self: Box<Self>,
|
||||
self,
|
||||
_communicate_allowed: bool,
|
||||
_ecx: &mut MiriInterpCx<'tcx>,
|
||||
) -> InterpResult<'tcx, io::Result<()>> {
|
||||
) -> InterpResult<'tcx, io::Result<()>>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
throw_unsup_format!("cannot close {}", self.name());
|
||||
}
|
||||
|
||||
@ -77,21 +197,13 @@ pub trait FileDescription: std::fmt::Debug + Any {
|
||||
}
|
||||
}
|
||||
|
||||
impl dyn FileDescription {
|
||||
#[inline(always)]
|
||||
pub fn downcast<T: Any>(&self) -> Option<&T> {
|
||||
(self as &dyn Any).downcast_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl FileDescription for io::Stdin {
|
||||
fn name(&self) -> &'static str {
|
||||
"stdin"
|
||||
}
|
||||
|
||||
fn read<'tcx>(
|
||||
&self,
|
||||
_self_ref: &FileDescriptionRef,
|
||||
self: FileDescriptionRef<Self>,
|
||||
communicate_allowed: bool,
|
||||
ptr: Pointer,
|
||||
len: usize,
|
||||
@ -103,7 +215,7 @@ impl FileDescription for io::Stdin {
|
||||
// We want isolation mode to be deterministic, so we have to disallow all reads, even stdin.
|
||||
helpers::isolation_abort_error("`read` from stdin")?;
|
||||
}
|
||||
let result = Read::read(&mut { self }, &mut bytes);
|
||||
let result = Read::read(&mut &*self, &mut bytes);
|
||||
match result {
|
||||
Ok(read_size) => ecx.return_read_success(ptr, &bytes, read_size, dest),
|
||||
Err(e) => ecx.set_last_error_and_return(e, dest),
|
||||
@ -121,8 +233,7 @@ impl FileDescription for io::Stdout {
|
||||
}
|
||||
|
||||
fn write<'tcx>(
|
||||
&self,
|
||||
_self_ref: &FileDescriptionRef,
|
||||
self: FileDescriptionRef<Self>,
|
||||
_communicate_allowed: bool,
|
||||
ptr: Pointer,
|
||||
len: usize,
|
||||
@ -131,7 +242,7 @@ impl FileDescription for io::Stdout {
|
||||
) -> InterpResult<'tcx> {
|
||||
let bytes = ecx.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
|
||||
// We allow writing to stderr even with isolation enabled.
|
||||
let result = Write::write(&mut { self }, bytes);
|
||||
let result = Write::write(&mut &*self, bytes);
|
||||
// Stdout is buffered, flush to make sure it appears on the
|
||||
// screen. This is the write() syscall of the interpreted
|
||||
// program, we want it to correspond to a write() syscall on
|
||||
@ -155,8 +266,7 @@ impl FileDescription for io::Stderr {
|
||||
}
|
||||
|
||||
fn write<'tcx>(
|
||||
&self,
|
||||
_self_ref: &FileDescriptionRef,
|
||||
self: FileDescriptionRef<Self>,
|
||||
_communicate_allowed: bool,
|
||||
ptr: Pointer,
|
||||
len: usize,
|
||||
@ -166,7 +276,7 @@ impl FileDescription for io::Stderr {
|
||||
let bytes = ecx.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
|
||||
// We allow writing to stderr even with isolation enabled.
|
||||
// No need to flush, stderr is not buffered.
|
||||
let result = Write::write(&mut { self }, bytes);
|
||||
let result = Write::write(&mut &*self, bytes);
|
||||
match result {
|
||||
Ok(write_size) => ecx.return_write_success(write_size, dest),
|
||||
Err(e) => ecx.set_last_error_and_return(e, dest),
|
||||
@ -188,8 +298,7 @@ impl FileDescription for NullOutput {
|
||||
}
|
||||
|
||||
fn write<'tcx>(
|
||||
&self,
|
||||
_self_ref: &FileDescriptionRef,
|
||||
self: FileDescriptionRef<Self>,
|
||||
_communicate_allowed: bool,
|
||||
_ptr: Pointer,
|
||||
len: usize,
|
||||
@ -201,91 +310,10 @@ impl FileDescription for NullOutput {
|
||||
}
|
||||
}
|
||||
|
||||
/// Structure contains both the file description and its unique identifier.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FileDescWithId<T: FileDescription + ?Sized> {
|
||||
id: FdId,
|
||||
file_description: Box<T>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FileDescriptionRef(Rc<FileDescWithId<dyn FileDescription>>);
|
||||
|
||||
impl Deref for FileDescriptionRef {
|
||||
type Target = dyn FileDescription;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&*self.0.file_description
|
||||
}
|
||||
}
|
||||
|
||||
impl FileDescriptionRef {
|
||||
fn new(fd: impl FileDescription, id: FdId) -> Self {
|
||||
FileDescriptionRef(Rc::new(FileDescWithId { id, file_description: Box::new(fd) }))
|
||||
}
|
||||
|
||||
pub fn close<'tcx>(
|
||||
self,
|
||||
communicate_allowed: bool,
|
||||
ecx: &mut MiriInterpCx<'tcx>,
|
||||
) -> InterpResult<'tcx, io::Result<()>> {
|
||||
// Destroy this `Rc` using `into_inner` so we can call `close` instead of
|
||||
// implicitly running the destructor of the file description.
|
||||
let id = self.get_id();
|
||||
match Rc::into_inner(self.0) {
|
||||
Some(fd) => {
|
||||
// Remove entry from the global epoll_event_interest table.
|
||||
ecx.machine.epoll_interests.remove(id);
|
||||
|
||||
fd.file_description.close(communicate_allowed, ecx)
|
||||
}
|
||||
None => interp_ok(Ok(())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn downgrade(&self) -> WeakFileDescriptionRef {
|
||||
WeakFileDescriptionRef { weak_ref: Rc::downgrade(&self.0) }
|
||||
}
|
||||
|
||||
pub fn get_id(&self) -> FdId {
|
||||
self.0.id
|
||||
}
|
||||
}
|
||||
|
||||
/// Holds a weak reference to the actual file description.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct WeakFileDescriptionRef {
|
||||
weak_ref: Weak<FileDescWithId<dyn FileDescription>>,
|
||||
}
|
||||
|
||||
impl WeakFileDescriptionRef {
|
||||
pub fn upgrade(&self) -> Option<FileDescriptionRef> {
|
||||
if let Some(file_desc_with_id) = self.weak_ref.upgrade() {
|
||||
return Some(FileDescriptionRef(file_desc_with_id));
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitProvenance for WeakFileDescriptionRef {
|
||||
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
|
||||
// A weak reference can never be the only reference to some pointer or place.
|
||||
// Since the actual file description is tracked by strong ref somewhere,
|
||||
// it is ok to make this a NOP operation.
|
||||
}
|
||||
}
|
||||
|
||||
/// A unique id for file descriptions. While we could use the address, considering that
|
||||
/// is definitely unique, the address would expose interpreter internal state when used
|
||||
/// for sorting things. So instead we generate a unique id per file description is the name
|
||||
/// for all `dup`licates and is never reused.
|
||||
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub struct FdId(usize);
|
||||
|
||||
/// The file descriptor table
|
||||
#[derive(Debug)]
|
||||
pub struct FdTable {
|
||||
pub fds: BTreeMap<i32, FileDescriptionRef>,
|
||||
pub fds: BTreeMap<i32, DynFileDescriptionRef>,
|
||||
/// Unique identifier for file description, used to differentiate between various file description.
|
||||
next_file_description_id: FdId,
|
||||
}
|
||||
@ -313,8 +341,9 @@ impl FdTable {
|
||||
fds
|
||||
}
|
||||
|
||||
pub fn new_ref(&mut self, fd: impl FileDescription) -> FileDescriptionRef {
|
||||
let file_handle = FileDescriptionRef::new(fd, self.next_file_description_id);
|
||||
pub fn new_ref<T: FileDescription>(&mut self, fd: T) -> FileDescriptionRef<T> {
|
||||
let file_handle =
|
||||
FileDescriptionRef(Rc::new(FdIdWith { id: self.next_file_description_id, inner: fd }));
|
||||
self.next_file_description_id = FdId(self.next_file_description_id.0.strict_add(1));
|
||||
file_handle
|
||||
}
|
||||
@ -325,12 +354,16 @@ impl FdTable {
|
||||
self.insert(fd_ref)
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, fd_ref: FileDescriptionRef) -> i32 {
|
||||
pub fn insert(&mut self, fd_ref: DynFileDescriptionRef) -> i32 {
|
||||
self.insert_with_min_num(fd_ref, 0)
|
||||
}
|
||||
|
||||
/// Insert a file description, giving it a file descriptor that is at least `min_fd_num`.
|
||||
pub fn insert_with_min_num(&mut self, file_handle: FileDescriptionRef, min_fd_num: i32) -> i32 {
|
||||
pub fn insert_with_min_num(
|
||||
&mut self,
|
||||
file_handle: DynFileDescriptionRef,
|
||||
min_fd_num: i32,
|
||||
) -> i32 {
|
||||
// Find the lowest unused FD, starting from min_fd. If the first such unused FD is in
|
||||
// between used FDs, the find_map combinator will return it. If the first such unused FD
|
||||
// is after all other used FDs, the find_map combinator will return None, and we will use
|
||||
@ -356,12 +389,12 @@ impl FdTable {
|
||||
new_fd_num
|
||||
}
|
||||
|
||||
pub fn get(&self, fd_num: i32) -> Option<FileDescriptionRef> {
|
||||
pub fn get(&self, fd_num: i32) -> Option<DynFileDescriptionRef> {
|
||||
let fd = self.fds.get(&fd_num)?;
|
||||
Some(fd.clone())
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, fd_num: i32) -> Option<FileDescriptionRef> {
|
||||
pub fn remove(&mut self, fd_num: i32) -> Option<DynFileDescriptionRef> {
|
||||
self.fds.remove(&fd_num)
|
||||
}
|
||||
|
||||
|
@ -72,7 +72,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
}
|
||||
// Functions with no declared return type (i.e., the default return)
|
||||
// have the output_type `Tuple([])`.
|
||||
ty::Tuple(t_list) if t_list.len() == 0 => {
|
||||
ty::Tuple(t_list) if t_list.is_empty() => {
|
||||
unsafe { ffi::call::<()>(ptr, libffi_args.as_slice()) };
|
||||
return interp_ok(ImmTy::uninit(dest.layout));
|
||||
}
|
||||
|
@ -331,8 +331,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
Some((TimeoutClock::Monotonic, TimeoutAnchor::Relative, duration)),
|
||||
callback!(
|
||||
@capture<'tcx> {}
|
||||
@unblock = |_this| { panic!("sleeping thread unblocked before time is up") }
|
||||
@timeout = |_this| { interp_ok(()) }
|
||||
|_this, unblock: UnblockKind| {
|
||||
assert_eq!(unblock, UnblockKind::TimedOut);
|
||||
interp_ok(())
|
||||
}
|
||||
),
|
||||
);
|
||||
interp_ok(Scalar::from_i32(0))
|
||||
@ -353,8 +355,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
Some((TimeoutClock::Monotonic, TimeoutAnchor::Relative, duration)),
|
||||
callback!(
|
||||
@capture<'tcx> {}
|
||||
@unblock = |_this| { panic!("sleeping thread unblocked before time is up") }
|
||||
@timeout = |_this| { interp_ok(()) }
|
||||
|_this, unblock: UnblockKind| {
|
||||
assert_eq!(unblock, UnblockKind::TimedOut);
|
||||
interp_ok(())
|
||||
}
|
||||
),
|
||||
);
|
||||
interp_ok(())
|
||||
|
@ -88,7 +88,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
// If old_fd and new_fd point to the same description, then `dup_fd` ensures we keep the underlying file description alive.
|
||||
if let Some(old_new_fd) = this.machine.fds.fds.insert(new_fd_num, fd) {
|
||||
// Ignore close error (not interpreter's) according to dup2() doc.
|
||||
old_new_fd.close(this.machine.communicate(), this)?.ok();
|
||||
old_new_fd.close_ref(this.machine.communicate(), this)?.ok();
|
||||
}
|
||||
}
|
||||
interp_ok(Scalar::from_i32(new_fd_num))
|
||||
@ -122,7 +122,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
};
|
||||
|
||||
let result = fd.as_unix().flock(this.machine.communicate(), parsed_op)?;
|
||||
drop(fd);
|
||||
// return `0` if flock is successful
|
||||
let result = result.map(|()| 0i32);
|
||||
interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
|
||||
@ -198,7 +197,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
let Some(fd) = this.machine.fds.remove(fd_num) else {
|
||||
return this.set_last_error_and_return_i32(LibcError("EBADF"));
|
||||
};
|
||||
let result = fd.close(this.machine.communicate(), this)?;
|
||||
let result = fd.close_ref(this.machine.communicate(), this)?;
|
||||
// return `0` if close is successful
|
||||
let result = result.map(|()| 0i32);
|
||||
interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
|
||||
@ -246,7 +245,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
// `usize::MAX` because it is bounded by the host's `isize`.
|
||||
|
||||
match offset {
|
||||
None => fd.read(&fd, communicate, buf, count, dest, this)?,
|
||||
None => fd.read(communicate, buf, count, dest, this)?,
|
||||
Some(offset) => {
|
||||
let Ok(offset) = u64::try_from(offset) else {
|
||||
return this.set_last_error_and_return(LibcError("EINVAL"), dest);
|
||||
@ -286,7 +285,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
};
|
||||
|
||||
match offset {
|
||||
None => fd.write(&fd, communicate, buf, count, dest, this)?,
|
||||
None => fd.write(communicate, buf, count, dest, this)?,
|
||||
Some(offset) => {
|
||||
let Ok(offset) = u64::try_from(offset) else {
|
||||
return this.set_last_error_and_return(LibcError("EINVAL"), dest);
|
||||
|
@ -109,56 +109,54 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
let this = self.eval_context_mut();
|
||||
|
||||
// See `fn emulate_foreign_item_inner` in `shims/foreign_items.rs` for the general pattern.
|
||||
#[rustfmt::skip]
|
||||
match link_name.as_str() {
|
||||
// Environment related shims
|
||||
"getenv" => {
|
||||
let [name] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [name] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let result = this.getenv(name)?;
|
||||
this.write_pointer(result, dest)?;
|
||||
}
|
||||
"unsetenv" => {
|
||||
let [name] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [name] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let result = this.unsetenv(name)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
"setenv" => {
|
||||
let [name, value, overwrite] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [name, value, overwrite] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
this.read_scalar(overwrite)?.to_i32()?;
|
||||
let result = this.setenv(name, value)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
"getcwd" => {
|
||||
let [buf, size] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [buf, size] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let result = this.getcwd(buf, size)?;
|
||||
this.write_pointer(result, dest)?;
|
||||
}
|
||||
"chdir" => {
|
||||
let [path] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [path] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let result = this.chdir(path)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
"getpid" => {
|
||||
let [] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let result = this.getpid()?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
"sysconf" => {
|
||||
let [val] =
|
||||
this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let [val] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let result = this.sysconf(val)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
// File descriptors
|
||||
"read" => {
|
||||
let [fd, buf, count] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [fd, buf, count] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let fd = this.read_scalar(fd)?.to_i32()?;
|
||||
let buf = this.read_pointer(buf)?;
|
||||
let count = this.read_target_usize(count)?;
|
||||
this.read(fd, buf, count, None, dest)?;
|
||||
}
|
||||
"write" => {
|
||||
let [fd, buf, n] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [fd, buf, n] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let fd = this.read_scalar(fd)?.to_i32()?;
|
||||
let buf = this.read_pointer(buf)?;
|
||||
let count = this.read_target_usize(n)?;
|
||||
@ -166,7 +164,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
this.write(fd, buf, count, None, dest)?;
|
||||
}
|
||||
"pread" => {
|
||||
let [fd, buf, count, offset] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [fd, buf, count, offset] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let fd = this.read_scalar(fd)?.to_i32()?;
|
||||
let buf = this.read_pointer(buf)?;
|
||||
let count = this.read_target_usize(count)?;
|
||||
@ -174,7 +172,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
this.read(fd, buf, count, Some(offset), dest)?;
|
||||
}
|
||||
"pwrite" => {
|
||||
let [fd, buf, n, offset] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [fd, buf, n, offset] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let fd = this.read_scalar(fd)?.to_i32()?;
|
||||
let buf = this.read_pointer(buf)?;
|
||||
let count = this.read_target_usize(n)?;
|
||||
@ -183,49 +181,51 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
this.write(fd, buf, count, Some(offset), dest)?;
|
||||
}
|
||||
"pread64" => {
|
||||
let [fd, buf, count, offset] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [fd, buf, count, offset] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let fd = this.read_scalar(fd)?.to_i32()?;
|
||||
let buf = this.read_pointer(buf)?;
|
||||
let count = this.read_target_usize(count)?;
|
||||
let offset = this.read_scalar(offset)?.to_int(this.libc_ty_layout("off64_t").size)?;
|
||||
let offset =
|
||||
this.read_scalar(offset)?.to_int(this.libc_ty_layout("off64_t").size)?;
|
||||
this.read(fd, buf, count, Some(offset), dest)?;
|
||||
}
|
||||
"pwrite64" => {
|
||||
let [fd, buf, n, offset] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [fd, buf, n, offset] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let fd = this.read_scalar(fd)?.to_i32()?;
|
||||
let buf = this.read_pointer(buf)?;
|
||||
let count = this.read_target_usize(n)?;
|
||||
let offset = this.read_scalar(offset)?.to_int(this.libc_ty_layout("off64_t").size)?;
|
||||
let offset =
|
||||
this.read_scalar(offset)?.to_int(this.libc_ty_layout("off64_t").size)?;
|
||||
trace!("Called pwrite64({:?}, {:?}, {:?}, {:?})", fd, buf, count, offset);
|
||||
this.write(fd, buf, count, Some(offset), dest)?;
|
||||
}
|
||||
"close" => {
|
||||
let [fd] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [fd] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let result = this.close(fd)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
"fcntl" => {
|
||||
// `fcntl` is variadic. The argument count is checked based on the first argument
|
||||
// in `this.fcntl()`, so we do not use `check_shim` here.
|
||||
this.check_abi_and_shim_symbol_clash(abi, Conv::C , link_name)?;
|
||||
this.check_abi_and_shim_symbol_clash(abi, Conv::C, link_name)?;
|
||||
let result = this.fcntl(args)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
"dup" => {
|
||||
let [old_fd] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [old_fd] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let old_fd = this.read_scalar(old_fd)?.to_i32()?;
|
||||
let new_fd = this.dup(old_fd)?;
|
||||
this.write_scalar(new_fd, dest)?;
|
||||
}
|
||||
"dup2" => {
|
||||
let [old_fd, new_fd] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [old_fd, new_fd] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let old_fd = this.read_scalar(old_fd)?.to_i32()?;
|
||||
let new_fd = this.read_scalar(new_fd)?.to_i32()?;
|
||||
let result = this.dup2(old_fd, new_fd)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
"flock" => {
|
||||
let [fd, op] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [fd, op] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let fd = this.read_scalar(fd)?.to_i32()?;
|
||||
let op = this.read_scalar(op)?.to_i32()?;
|
||||
let result = this.flock(fd, op)?;
|
||||
@ -234,48 +234,49 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
|
||||
// File and file system access
|
||||
"open" | "open64" => {
|
||||
// `open` is variadic, the third argument is only present when the second argument has O_CREAT (or on linux O_TMPFILE, but miri doesn't support that) set
|
||||
this.check_abi_and_shim_symbol_clash(abi, Conv::C , link_name)?;
|
||||
// `open` is variadic, the third argument is only present when the second argument
|
||||
// has O_CREAT (or on linux O_TMPFILE, but miri doesn't support that) set
|
||||
this.check_abi_and_shim_symbol_clash(abi, Conv::C, link_name)?;
|
||||
let result = this.open(args)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
"unlink" => {
|
||||
let [path] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [path] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let result = this.unlink(path)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
"symlink" => {
|
||||
let [target, linkpath] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [target, linkpath] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let result = this.symlink(target, linkpath)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
"rename" => {
|
||||
let [oldpath, newpath] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [oldpath, newpath] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let result = this.rename(oldpath, newpath)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
"mkdir" => {
|
||||
let [path, mode] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [path, mode] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let result = this.mkdir(path, mode)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
"rmdir" => {
|
||||
let [path] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [path] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let result = this.rmdir(path)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
"opendir" => {
|
||||
let [name] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [name] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let result = this.opendir(name)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
"closedir" => {
|
||||
let [dirp] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [dirp] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let result = this.closedir(dirp)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
"lseek64" => {
|
||||
let [fd, offset, whence] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [fd, offset, whence] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let fd = this.read_scalar(fd)?.to_i32()?;
|
||||
let offset = this.read_scalar(offset)?.to_i64()?;
|
||||
let whence = this.read_scalar(whence)?.to_i32()?;
|
||||
@ -283,7 +284,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
"lseek" => {
|
||||
let [fd, offset, whence] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [fd, offset, whence] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let fd = this.read_scalar(fd)?.to_i32()?;
|
||||
let offset = this.read_scalar(offset)?.to_int(this.libc_ty_layout("off_t").size)?;
|
||||
let whence = this.read_scalar(whence)?.to_i32()?;
|
||||
@ -291,39 +292,36 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
"ftruncate64" => {
|
||||
let [fd, length] =
|
||||
this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [fd, length] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let fd = this.read_scalar(fd)?.to_i32()?;
|
||||
let length = this.read_scalar(length)?.to_i64()?;
|
||||
let result = this.ftruncate64(fd, length.into())?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
"ftruncate" => {
|
||||
let [fd, length] =
|
||||
this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [fd, length] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let fd = this.read_scalar(fd)?.to_i32()?;
|
||||
let length = this.read_scalar(length)?.to_int(this.libc_ty_layout("off_t").size)?;
|
||||
let result = this.ftruncate64(fd, length)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
"fsync" => {
|
||||
let [fd] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [fd] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let result = this.fsync(fd)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
"fdatasync" => {
|
||||
let [fd] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [fd] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let result = this.fdatasync(fd)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
"readlink" => {
|
||||
let [pathname, buf, bufsize] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [pathname, buf, bufsize] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let result = this.readlink(pathname, buf, bufsize)?;
|
||||
this.write_scalar(Scalar::from_target_isize(result, this), dest)?;
|
||||
}
|
||||
"posix_fadvise" => {
|
||||
let [fd, offset, len, advice] =
|
||||
this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [fd, offset, len, advice] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
this.read_scalar(fd)?.to_i32()?;
|
||||
this.read_target_isize(offset)?;
|
||||
this.read_target_isize(len)?;
|
||||
@ -332,12 +330,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
this.write_null(dest)?;
|
||||
}
|
||||
"realpath" => {
|
||||
let [path, resolved_path] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [path, resolved_path] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let result = this.realpath(path, resolved_path)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
"mkstemp" => {
|
||||
let [template] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [template] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let result = this.mkstemp(template)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
@ -345,63 +343,59 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
// Unnamed sockets and pipes
|
||||
"socketpair" => {
|
||||
let [domain, type_, protocol, sv] =
|
||||
this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let result = this.socketpair(domain, type_, protocol, sv)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
"pipe" => {
|
||||
let [pipefd] =
|
||||
this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [pipefd] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let result = this.pipe2(pipefd, /*flags*/ None)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
"pipe2" => {
|
||||
// Currently this function does not exist on all Unixes, e.g. on macOS.
|
||||
if !matches!(&*this.tcx.sess.target.os, "linux" | "freebsd" | "solaris" | "illumos") {
|
||||
throw_unsup_format!(
|
||||
"`pipe2` is not supported on {}",
|
||||
this.tcx.sess.target.os
|
||||
);
|
||||
if !matches!(&*this.tcx.sess.target.os, "linux" | "freebsd" | "solaris" | "illumos")
|
||||
{
|
||||
throw_unsup_format!("`pipe2` is not supported on {}", this.tcx.sess.target.os);
|
||||
}
|
||||
let [pipefd, flags] =
|
||||
this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [pipefd, flags] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let result = this.pipe2(pipefd, Some(flags))?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
|
||||
// Time
|
||||
"gettimeofday" => {
|
||||
let [tv, tz] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [tv, tz] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let result = this.gettimeofday(tv, tz)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
"localtime_r" => {
|
||||
let [timep, result_op] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [timep, result_op] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let result = this.localtime_r(timep, result_op)?;
|
||||
this.write_pointer(result, dest)?;
|
||||
}
|
||||
"clock_gettime" => {
|
||||
let [clk_id, tp] =
|
||||
this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [clk_id, tp] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let result = this.clock_gettime(clk_id, tp)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
|
||||
// Allocation
|
||||
"posix_memalign" => {
|
||||
let [memptr, align, size] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [memptr, align, size] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let result = this.posix_memalign(memptr, align, size)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
|
||||
"mmap" => {
|
||||
let [addr, length, prot, flags, fd, offset] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [addr, length, prot, flags, fd, offset] =
|
||||
this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let offset = this.read_scalar(offset)?.to_int(this.libc_ty_layout("off_t").size)?;
|
||||
let ptr = this.mmap(addr, length, prot, flags, fd, offset)?;
|
||||
this.write_scalar(ptr, dest)?;
|
||||
}
|
||||
"munmap" => {
|
||||
let [addr, length] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [addr, length] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let result = this.munmap(addr, length)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
@ -414,8 +408,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
this.tcx.sess.target.os
|
||||
);
|
||||
}
|
||||
let [ptr, nmemb, size] =
|
||||
this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [ptr, nmemb, size] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let ptr = this.read_pointer(ptr)?;
|
||||
let nmemb = this.read_target_usize(nmemb)?;
|
||||
let size = this.read_target_usize(size)?;
|
||||
@ -438,19 +431,20 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
"aligned_alloc" => {
|
||||
// This is a C11 function, we assume all Unixes have it.
|
||||
// (MSVC explicitly does not support this.)
|
||||
let [align, size] =
|
||||
this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [align, size] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let res = this.aligned_alloc(align, size)?;
|
||||
this.write_pointer(res, dest)?;
|
||||
}
|
||||
|
||||
// Dynamic symbol loading
|
||||
"dlsym" => {
|
||||
let [handle, symbol] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [handle, symbol] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
this.read_target_usize(handle)?;
|
||||
let symbol = this.read_pointer(symbol)?;
|
||||
let name = this.read_c_str(symbol)?;
|
||||
if let Ok(name) = str::from_utf8(name) && is_dyn_sym(name, &this.tcx.sess.target.os) {
|
||||
if let Ok(name) = str::from_utf8(name)
|
||||
&& is_dyn_sym(name, &this.tcx.sess.target.os)
|
||||
{
|
||||
let ptr = this.fn_ptr(FnVal::Other(DynSym::from_str(name)));
|
||||
this.write_pointer(ptr, dest)?;
|
||||
} else {
|
||||
@ -460,7 +454,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
|
||||
// Thread-local storage
|
||||
"pthread_key_create" => {
|
||||
let [key, dtor] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [key, dtor] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let key_place = this.deref_pointer_as(key, this.libc_ty_layout("pthread_key_t"))?;
|
||||
let dtor = this.read_pointer(dtor)?;
|
||||
|
||||
@ -488,21 +482,21 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
this.write_null(dest)?;
|
||||
}
|
||||
"pthread_key_delete" => {
|
||||
let [key] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [key] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let key = this.read_scalar(key)?.to_bits(key.layout.size)?;
|
||||
this.machine.tls.delete_tls_key(key)?;
|
||||
// Return success (0)
|
||||
this.write_null(dest)?;
|
||||
}
|
||||
"pthread_getspecific" => {
|
||||
let [key] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [key] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let key = this.read_scalar(key)?.to_bits(key.layout.size)?;
|
||||
let active_thread = this.active_thread();
|
||||
let ptr = this.machine.tls.load_tls(key, active_thread, this)?;
|
||||
this.write_scalar(ptr, dest)?;
|
||||
}
|
||||
"pthread_setspecific" => {
|
||||
let [key, new_ptr] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [key, new_ptr] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let key = this.read_scalar(key)?.to_bits(key.layout.size)?;
|
||||
let active_thread = this.active_thread();
|
||||
let new_data = this.read_scalar(new_ptr)?;
|
||||
@ -514,151 +508,149 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
|
||||
// Synchronization primitives
|
||||
"pthread_mutexattr_init" => {
|
||||
let [attr] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [attr] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
this.pthread_mutexattr_init(attr)?;
|
||||
this.write_null(dest)?;
|
||||
}
|
||||
"pthread_mutexattr_settype" => {
|
||||
let [attr, kind] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [attr, kind] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let result = this.pthread_mutexattr_settype(attr, kind)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
"pthread_mutexattr_destroy" => {
|
||||
let [attr] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [attr] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
this.pthread_mutexattr_destroy(attr)?;
|
||||
this.write_null(dest)?;
|
||||
}
|
||||
"pthread_mutex_init" => {
|
||||
let [mutex, attr] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [mutex, attr] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
this.pthread_mutex_init(mutex, attr)?;
|
||||
this.write_null(dest)?;
|
||||
}
|
||||
"pthread_mutex_lock" => {
|
||||
let [mutex] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [mutex] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
this.pthread_mutex_lock(mutex, dest)?;
|
||||
}
|
||||
"pthread_mutex_trylock" => {
|
||||
let [mutex] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [mutex] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let result = this.pthread_mutex_trylock(mutex)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
"pthread_mutex_unlock" => {
|
||||
let [mutex] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [mutex] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let result = this.pthread_mutex_unlock(mutex)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
"pthread_mutex_destroy" => {
|
||||
let [mutex] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [mutex] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
this.pthread_mutex_destroy(mutex)?;
|
||||
this.write_int(0, dest)?;
|
||||
}
|
||||
"pthread_rwlock_rdlock" => {
|
||||
let [rwlock] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [rwlock] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
this.pthread_rwlock_rdlock(rwlock, dest)?;
|
||||
}
|
||||
"pthread_rwlock_tryrdlock" => {
|
||||
let [rwlock] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [rwlock] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let result = this.pthread_rwlock_tryrdlock(rwlock)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
"pthread_rwlock_wrlock" => {
|
||||
let [rwlock] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [rwlock] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
this.pthread_rwlock_wrlock(rwlock, dest)?;
|
||||
}
|
||||
"pthread_rwlock_trywrlock" => {
|
||||
let [rwlock] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [rwlock] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let result = this.pthread_rwlock_trywrlock(rwlock)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
"pthread_rwlock_unlock" => {
|
||||
let [rwlock] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [rwlock] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
this.pthread_rwlock_unlock(rwlock)?;
|
||||
this.write_null(dest)?;
|
||||
}
|
||||
"pthread_rwlock_destroy" => {
|
||||
let [rwlock] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [rwlock] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
this.pthread_rwlock_destroy(rwlock)?;
|
||||
this.write_null(dest)?;
|
||||
}
|
||||
"pthread_condattr_init" => {
|
||||
let [attr] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [attr] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
this.pthread_condattr_init(attr)?;
|
||||
this.write_null(dest)?;
|
||||
}
|
||||
"pthread_condattr_setclock" => {
|
||||
let [attr, clock_id] =
|
||||
this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [attr, clock_id] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let result = this.pthread_condattr_setclock(attr, clock_id)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
"pthread_condattr_getclock" => {
|
||||
let [attr, clock_id] =
|
||||
this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [attr, clock_id] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
this.pthread_condattr_getclock(attr, clock_id)?;
|
||||
this.write_null(dest)?;
|
||||
}
|
||||
"pthread_condattr_destroy" => {
|
||||
let [attr] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [attr] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
this.pthread_condattr_destroy(attr)?;
|
||||
this.write_null(dest)?;
|
||||
}
|
||||
"pthread_cond_init" => {
|
||||
let [cond, attr] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [cond, attr] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
this.pthread_cond_init(cond, attr)?;
|
||||
this.write_null(dest)?;
|
||||
}
|
||||
"pthread_cond_signal" => {
|
||||
let [cond] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [cond] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
this.pthread_cond_signal(cond)?;
|
||||
this.write_null(dest)?;
|
||||
}
|
||||
"pthread_cond_broadcast" => {
|
||||
let [cond] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [cond] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
this.pthread_cond_broadcast(cond)?;
|
||||
this.write_null(dest)?;
|
||||
}
|
||||
"pthread_cond_wait" => {
|
||||
let [cond, mutex] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [cond, mutex] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
this.pthread_cond_wait(cond, mutex, dest)?;
|
||||
}
|
||||
"pthread_cond_timedwait" => {
|
||||
let [cond, mutex, abstime] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [cond, mutex, abstime] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
this.pthread_cond_timedwait(cond, mutex, abstime, dest)?;
|
||||
}
|
||||
"pthread_cond_destroy" => {
|
||||
let [cond] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [cond] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
this.pthread_cond_destroy(cond)?;
|
||||
this.write_null(dest)?;
|
||||
}
|
||||
|
||||
// Threading
|
||||
"pthread_create" => {
|
||||
let [thread, attr, start, arg] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [thread, attr, start, arg] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
this.pthread_create(thread, attr, start, arg)?;
|
||||
this.write_null(dest)?;
|
||||
}
|
||||
"pthread_join" => {
|
||||
let [thread, retval] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [thread, retval] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let res = this.pthread_join(thread, retval)?;
|
||||
this.write_scalar(res, dest)?;
|
||||
}
|
||||
"pthread_detach" => {
|
||||
let [thread] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [thread] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let res = this.pthread_detach(thread)?;
|
||||
this.write_scalar(res, dest)?;
|
||||
}
|
||||
"pthread_self" => {
|
||||
let [] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let res = this.pthread_self()?;
|
||||
this.write_scalar(res, dest)?;
|
||||
}
|
||||
"sched_yield" => {
|
||||
let [] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
this.sched_yield()?;
|
||||
this.write_null(dest)?;
|
||||
}
|
||||
"nanosleep" => {
|
||||
let [req, rem] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [req, rem] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let result = this.nanosleep(req, rem)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
@ -671,8 +663,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
);
|
||||
}
|
||||
|
||||
let [pid, cpusetsize, mask] =
|
||||
this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [pid, cpusetsize, mask] = this.check_shim(abi, Conv::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)?;
|
||||
@ -680,7 +671,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
// 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)"),
|
||||
_ =>
|
||||
throw_unsup_format!(
|
||||
"`sched_getaffinity` is only supported with a pid of 0 (indicating the current thread)"
|
||||
),
|
||||
};
|
||||
|
||||
// The mask is stored in chunks, and the size must be a whole number of chunks.
|
||||
@ -694,7 +688,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
} else if let Some(cpuset) = this.machine.thread_cpu_affinity.get(&thread_id) {
|
||||
let cpuset = cpuset.clone();
|
||||
// we only copy whole chunks of size_of::<c_ulong>()
|
||||
let byte_count = Ord::min(cpuset.as_slice().len(), cpusetsize.try_into().unwrap());
|
||||
let byte_count =
|
||||
Ord::min(cpuset.as_slice().len(), cpusetsize.try_into().unwrap());
|
||||
this.write_bytes_ptr(mask, cpuset.as_slice()[..byte_count].iter().copied())?;
|
||||
this.write_null(dest)?;
|
||||
} else {
|
||||
@ -711,8 +706,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
);
|
||||
}
|
||||
|
||||
let [pid, cpusetsize, mask] =
|
||||
this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [pid, cpusetsize, mask] = this.check_shim(abi, Conv::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)?;
|
||||
@ -720,7 +714,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
// 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)"),
|
||||
_ =>
|
||||
throw_unsup_format!(
|
||||
"`sched_setaffinity` is only supported with a pid of 0 (indicating the current thread)"
|
||||
),
|
||||
};
|
||||
|
||||
if this.ptr_is_null(mask)? {
|
||||
@ -729,7 +726,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
// NOTE: cpusetsize might be smaller than `CpuAffinityMask::CPU_MASK_BYTES`.
|
||||
// Any unspecified bytes are treated as zero here (none of the CPUs are configured).
|
||||
// This is not exactly documented, so we assume that this is the behavior in practice.
|
||||
let bits_slice = this.read_bytes_ptr_strip_provenance(mask, Size::from_bytes(cpusetsize))?;
|
||||
let bits_slice =
|
||||
this.read_bytes_ptr_strip_provenance(mask, Size::from_bytes(cpusetsize))?;
|
||||
// This ignores the bytes beyond `CpuAffinityMask::CPU_MASK_BYTES`
|
||||
let bits_array: [u8; CpuAffinityMask::CPU_MASK_BYTES] =
|
||||
std::array::from_fn(|i| bits_slice.get(i).copied().unwrap_or(0));
|
||||
@ -748,12 +746,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
|
||||
// Miscellaneous
|
||||
"isatty" => {
|
||||
let [fd] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [fd] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let result = this.isatty(fd)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
"pthread_atfork" => {
|
||||
let [prepare, parent, child] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [prepare, parent, child] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
this.read_pointer(prepare)?;
|
||||
this.read_pointer(parent)?;
|
||||
this.read_pointer(child)?;
|
||||
@ -763,15 +761,17 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
"getentropy" => {
|
||||
// This function is non-standard but exists with the same signature and behavior on
|
||||
// Linux, macOS, FreeBSD and Solaris/Illumos.
|
||||
if !matches!(&*this.tcx.sess.target.os, "linux" | "macos" | "freebsd" | "illumos" | "solaris" | "android") {
|
||||
if !matches!(
|
||||
&*this.tcx.sess.target.os,
|
||||
"linux" | "macos" | "freebsd" | "illumos" | "solaris" | "android"
|
||||
) {
|
||||
throw_unsup_format!(
|
||||
"`getentropy` is not supported on {}",
|
||||
this.tcx.sess.target.os
|
||||
);
|
||||
}
|
||||
|
||||
let [buf, bufsize] =
|
||||
this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [buf, bufsize] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let buf = this.read_pointer(buf)?;
|
||||
let bufsize = this.read_target_usize(bufsize)?;
|
||||
|
||||
@ -789,8 +789,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
}
|
||||
|
||||
"strerror_r" => {
|
||||
let [errnum, buf, buflen] =
|
||||
this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let [errnum, buf, buflen] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let result = this.strerror_r(errnum, buf, buflen)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
@ -798,14 +797,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
"getrandom" => {
|
||||
// This function is non-standard but exists with the same signature and behavior on
|
||||
// Linux, FreeBSD and Solaris/Illumos.
|
||||
if !matches!(&*this.tcx.sess.target.os, "linux" | "freebsd" | "illumos" | "solaris" | "android") {
|
||||
if !matches!(
|
||||
&*this.tcx.sess.target.os,
|
||||
"linux" | "freebsd" | "illumos" | "solaris" | "android"
|
||||
) {
|
||||
throw_unsup_format!(
|
||||
"`getrandom` is not supported on {}",
|
||||
this.tcx.sess.target.os
|
||||
);
|
||||
}
|
||||
let [ptr, len, flags] =
|
||||
this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [ptr, len, flags] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let ptr = this.read_pointer(ptr)?;
|
||||
let len = this.read_target_usize(len)?;
|
||||
let _flags = this.read_scalar(flags)?.to_i32()?;
|
||||
@ -822,7 +823,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
this.tcx.sess.target.os
|
||||
);
|
||||
}
|
||||
let [ptr, len] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [ptr, len] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let ptr = this.read_pointer(ptr)?;
|
||||
let len = this.read_target_usize(len)?;
|
||||
this.gen_random(ptr, len)?;
|
||||
@ -841,7 +842,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
// For arm32 they did something custom, but similar enough that the same
|
||||
// `_Unwind_RaiseException` impl in miri should work:
|
||||
// https://github.com/ARM-software/abi-aa/blob/main/ehabi32/ehabi32.rst
|
||||
if !matches!(&*this.tcx.sess.target.os, "linux" | "freebsd" | "illumos" | "solaris" | "android" | "macos") {
|
||||
if !matches!(
|
||||
&*this.tcx.sess.target.os,
|
||||
"linux" | "freebsd" | "illumos" | "solaris" | "android" | "macos"
|
||||
) {
|
||||
throw_unsup_format!(
|
||||
"`_Unwind_RaiseException` is not supported on {}",
|
||||
this.tcx.sess.target.os
|
||||
@ -853,43 +857,42 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
return interp_ok(EmulateItemResult::NeedsUnwind);
|
||||
}
|
||||
"getuid" => {
|
||||
let [] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let [] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
// For now, just pretend we always have this fixed UID.
|
||||
this.write_int(UID, dest)?;
|
||||
}
|
||||
|
||||
// Incomplete shims that we "stub out" just to get pre-main initialization code to work.
|
||||
// These shims are enabled only when the caller is in the standard library.
|
||||
"pthread_attr_getguardsize"
|
||||
if this.frame_in_std() => {
|
||||
let [_attr, guard_size] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
"pthread_attr_getguardsize" if this.frame_in_std() => {
|
||||
let [_attr, guard_size] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let guard_size = this.deref_pointer(guard_size)?;
|
||||
let guard_size_layout = this.libc_ty_layout("size_t");
|
||||
this.write_scalar(Scalar::from_uint(this.machine.page_size, guard_size_layout.size), &guard_size)?;
|
||||
this.write_scalar(
|
||||
Scalar::from_uint(this.machine.page_size, guard_size_layout.size),
|
||||
&guard_size,
|
||||
)?;
|
||||
|
||||
// Return success (`0`).
|
||||
this.write_null(dest)?;
|
||||
}
|
||||
|
||||
| "pthread_attr_init"
|
||||
| "pthread_attr_destroy"
|
||||
if this.frame_in_std() => {
|
||||
let [_] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
"pthread_attr_init" | "pthread_attr_destroy" if this.frame_in_std() => {
|
||||
let [_] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
this.write_null(dest)?;
|
||||
}
|
||||
| "pthread_attr_setstacksize"
|
||||
if this.frame_in_std() => {
|
||||
let [_, _] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
"pthread_attr_setstacksize" if this.frame_in_std() => {
|
||||
let [_, _] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
this.write_null(dest)?;
|
||||
}
|
||||
|
||||
"pthread_attr_getstack"
|
||||
if this.frame_in_std() => {
|
||||
"pthread_attr_getstack" if this.frame_in_std() => {
|
||||
// We don't support "pthread_attr_setstack", so we just pretend all stacks have the same values here.
|
||||
// Hence we can mostly ignore the input `attr_place`.
|
||||
let [attr_place, addr_place, size_place] =
|
||||
this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
let _attr_place = this.deref_pointer_as(attr_place, this.libc_ty_layout("pthread_attr_t"))?;
|
||||
this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let _attr_place =
|
||||
this.deref_pointer_as(attr_place, this.libc_ty_layout("pthread_attr_t"))?;
|
||||
let addr_place = this.deref_pointer(addr_place)?;
|
||||
let size_place = this.deref_pointer(size_place)?;
|
||||
|
||||
@ -906,24 +909,19 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
this.write_null(dest)?;
|
||||
}
|
||||
|
||||
| "signal"
|
||||
| "sigaltstack"
|
||||
if this.frame_in_std() => {
|
||||
let [_, _] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
"signal" | "sigaltstack" if this.frame_in_std() => {
|
||||
let [_, _] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
this.write_null(dest)?;
|
||||
}
|
||||
| "sigaction"
|
||||
| "mprotect"
|
||||
if this.frame_in_std() => {
|
||||
let [_, _, _] = this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
"sigaction" | "mprotect" if this.frame_in_std() => {
|
||||
let [_, _, _] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
this.write_null(dest)?;
|
||||
}
|
||||
|
||||
"getpwuid_r" | "__posix_getpwuid_r"
|
||||
if this.frame_in_std() => {
|
||||
"getpwuid_r" | "__posix_getpwuid_r" if this.frame_in_std() => {
|
||||
// getpwuid_r is the standard name, __posix_getpwuid_r is used on solarish
|
||||
let [uid, pwd, buf, buflen, result] =
|
||||
this.check_shim(abi, Conv::C , link_name, args)?;
|
||||
this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
this.check_no_isolation("`getpwuid_r`")?;
|
||||
|
||||
let uid = this.read_scalar(uid)?.to_u32()?;
|
||||
@ -961,11 +959,26 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
_ => {
|
||||
let target_os = &*this.tcx.sess.target.os;
|
||||
return match target_os {
|
||||
"android" => android::EvalContextExt::emulate_foreign_item_inner(this, link_name, abi, args, dest),
|
||||
"freebsd" => freebsd::EvalContextExt::emulate_foreign_item_inner(this, link_name, abi, args, dest),
|
||||
"linux" => linux::EvalContextExt::emulate_foreign_item_inner(this, link_name, abi, args, dest),
|
||||
"macos" => macos::EvalContextExt::emulate_foreign_item_inner(this, link_name, abi, args, dest),
|
||||
"solaris" | "illumos" => solarish::EvalContextExt::emulate_foreign_item_inner(this, link_name, abi, args, dest),
|
||||
"android" =>
|
||||
android::EvalContextExt::emulate_foreign_item_inner(
|
||||
this, link_name, abi, args, dest,
|
||||
),
|
||||
"freebsd" =>
|
||||
freebsd::EvalContextExt::emulate_foreign_item_inner(
|
||||
this, link_name, abi, args, dest,
|
||||
),
|
||||
"linux" =>
|
||||
linux::EvalContextExt::emulate_foreign_item_inner(
|
||||
this, link_name, abi, args, dest,
|
||||
),
|
||||
"macos" =>
|
||||
macos::EvalContextExt::emulate_foreign_item_inner(
|
||||
this, link_name, abi, args, dest,
|
||||
),
|
||||
"solaris" | "illumos" =>
|
||||
solarish::EvalContextExt::emulate_foreign_item_inner(
|
||||
this, link_name, abi, args, dest,
|
||||
),
|
||||
_ => interp_ok(EmulateItemResult::NotSupported),
|
||||
};
|
||||
}
|
||||
|
@ -21,29 +21,38 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
let this = self.eval_context_mut();
|
||||
match link_name.as_str() {
|
||||
// Threading
|
||||
"pthread_set_name_np" => {
|
||||
"pthread_setname_np" => {
|
||||
let [thread, name] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let max_len = usize::MAX; // FreeBSD does not seem to have a limit.
|
||||
// FreeBSD's pthread_set_name_np does not return anything.
|
||||
this.pthread_setname_np(
|
||||
let res = match this.pthread_setname_np(
|
||||
this.read_scalar(thread)?,
|
||||
this.read_scalar(name)?,
|
||||
max_len,
|
||||
/* truncate */ false,
|
||||
)?;
|
||||
)? {
|
||||
ThreadNameResult::Ok => Scalar::from_u32(0),
|
||||
ThreadNameResult::NameTooLong => unreachable!(),
|
||||
ThreadNameResult::ThreadNotFound => this.eval_libc("ESRCH"),
|
||||
};
|
||||
this.write_scalar(res, dest)?;
|
||||
}
|
||||
"pthread_get_name_np" => {
|
||||
"pthread_getname_np" => {
|
||||
let [thread, name, len] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
// FreeBSD's pthread_get_name_np does not return anything
|
||||
// and uses strlcpy, which truncates the resulting value,
|
||||
// FreeBSD's pthread_getname_np uses strlcpy, which truncates the resulting value,
|
||||
// but always adds a null terminator (except for zero-sized buffers).
|
||||
// https://github.com/freebsd/freebsd-src/blob/c2d93a803acef634bd0eede6673aeea59e90c277/lib/libthr/thread/thr_info.c#L119-L144
|
||||
this.pthread_getname_np(
|
||||
let res = match this.pthread_getname_np(
|
||||
this.read_scalar(thread)?,
|
||||
this.read_scalar(name)?,
|
||||
this.read_scalar(len)?,
|
||||
/* truncate */ true,
|
||||
)?;
|
||||
)? {
|
||||
ThreadNameResult::Ok => Scalar::from_u32(0),
|
||||
// `NameTooLong` is possible when the buffer is zero sized,
|
||||
ThreadNameResult::NameTooLong => Scalar::from_u32(0),
|
||||
ThreadNameResult::ThreadNotFound => this.eval_libc("ESRCH"),
|
||||
};
|
||||
this.write_scalar(res, dest)?;
|
||||
}
|
||||
|
||||
// File related shims
|
||||
|
@ -31,8 +31,7 @@ impl FileDescription for FileHandle {
|
||||
}
|
||||
|
||||
fn read<'tcx>(
|
||||
&self,
|
||||
_self_ref: &FileDescriptionRef,
|
||||
self: FileDescriptionRef<Self>,
|
||||
communicate_allowed: bool,
|
||||
ptr: Pointer,
|
||||
len: usize,
|
||||
@ -49,8 +48,7 @@ impl FileDescription for FileHandle {
|
||||
}
|
||||
|
||||
fn write<'tcx>(
|
||||
&self,
|
||||
_self_ref: &FileDescriptionRef,
|
||||
self: FileDescriptionRef<Self>,
|
||||
communicate_allowed: bool,
|
||||
ptr: Pointer,
|
||||
len: usize,
|
||||
@ -76,7 +74,7 @@ impl FileDescription for FileHandle {
|
||||
}
|
||||
|
||||
fn close<'tcx>(
|
||||
self: Box<Self>,
|
||||
self,
|
||||
communicate_allowed: bool,
|
||||
_ecx: &mut MiriInterpCx<'tcx>,
|
||||
) -> InterpResult<'tcx, io::Result<()>> {
|
||||
@ -87,7 +85,7 @@ impl FileDescription for FileHandle {
|
||||
// to handle possible errors correctly.
|
||||
let result = self.file.sync_all();
|
||||
// Now we actually close the file and return the result.
|
||||
drop(*self);
|
||||
drop(self.file);
|
||||
interp_ok(result)
|
||||
} else {
|
||||
// We drop the file, this closes it but ignores any errors
|
||||
@ -96,7 +94,7 @@ impl FileDescription for FileHandle {
|
||||
// `/dev/urandom` which are read-only. Check
|
||||
// https://github.com/rust-lang/miri/issues/999#issuecomment-568920439
|
||||
// for a deeper discussion.
|
||||
drop(*self);
|
||||
drop(self.file);
|
||||
interp_ok(Ok(()))
|
||||
}
|
||||
}
|
||||
@ -1311,22 +1309,19 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
};
|
||||
|
||||
// FIXME: Support ftruncate64 for all FDs
|
||||
let FileHandle { file, writable } = fd.downcast::<FileHandle>().ok_or_else(|| {
|
||||
let file = fd.downcast::<FileHandle>().ok_or_else(|| {
|
||||
err_unsup_format!("`ftruncate64` is only supported on file-backed file descriptors")
|
||||
})?;
|
||||
|
||||
if *writable {
|
||||
if file.writable {
|
||||
if let Ok(length) = length.try_into() {
|
||||
let result = file.set_len(length);
|
||||
drop(fd);
|
||||
let result = file.file.set_len(length);
|
||||
let result = this.try_unwrap_io_result(result.map(|_| 0i32))?;
|
||||
interp_ok(Scalar::from_i32(result))
|
||||
} else {
|
||||
drop(fd);
|
||||
this.set_last_error_and_return_i32(LibcError("EINVAL"))
|
||||
}
|
||||
} else {
|
||||
drop(fd);
|
||||
// The file is not writable
|
||||
this.set_last_error_and_return_i32(LibcError("EINVAL"))
|
||||
}
|
||||
@ -1358,11 +1353,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
return this.set_last_error_and_return_i32(LibcError("EBADF"));
|
||||
};
|
||||
// Only regular files support synchronization.
|
||||
let FileHandle { file, writable } = fd.downcast::<FileHandle>().ok_or_else(|| {
|
||||
let file = fd.downcast::<FileHandle>().ok_or_else(|| {
|
||||
err_unsup_format!("`fsync` is only supported on file-backed file descriptors")
|
||||
})?;
|
||||
let io_result = maybe_sync_file(file, *writable, File::sync_all);
|
||||
drop(fd);
|
||||
let io_result = maybe_sync_file(&file.file, file.writable, File::sync_all);
|
||||
interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
|
||||
}
|
||||
|
||||
@ -1382,11 +1376,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
return this.set_last_error_and_return_i32(LibcError("EBADF"));
|
||||
};
|
||||
// Only regular files support synchronization.
|
||||
let FileHandle { file, writable } = fd.downcast::<FileHandle>().ok_or_else(|| {
|
||||
let file = fd.downcast::<FileHandle>().ok_or_else(|| {
|
||||
err_unsup_format!("`fdatasync` is only supported on file-backed file descriptors")
|
||||
})?;
|
||||
let io_result = maybe_sync_file(file, *writable, File::sync_data);
|
||||
drop(fd);
|
||||
let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
|
||||
interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
|
||||
}
|
||||
|
||||
@ -1425,11 +1418,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
return this.set_last_error_and_return_i32(LibcError("EBADF"));
|
||||
};
|
||||
// Only regular files support synchronization.
|
||||
let FileHandle { file, writable } = fd.downcast::<FileHandle>().ok_or_else(|| {
|
||||
let file = fd.downcast::<FileHandle>().ok_or_else(|| {
|
||||
err_unsup_format!("`sync_data_range` is only supported on file-backed file descriptors")
|
||||
})?;
|
||||
let io_result = maybe_sync_file(file, *writable, File::sync_data);
|
||||
drop(fd);
|
||||
let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
|
||||
interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
|
||||
}
|
||||
|
||||
|
@ -5,12 +5,14 @@ use std::rc::{Rc, Weak};
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::concurrency::VClock;
|
||||
use crate::shims::files::{FdId, FileDescription, FileDescriptionRef, WeakFileDescriptionRef};
|
||||
use crate::shims::files::{
|
||||
DynFileDescriptionRef, FdId, FileDescription, FileDescriptionRef, WeakFileDescriptionRef,
|
||||
};
|
||||
use crate::shims::unix::UnixFileDescription;
|
||||
use crate::*;
|
||||
|
||||
/// An `Epoll` file descriptor connects file handles and epoll events
|
||||
#[derive(Clone, Debug, Default)]
|
||||
#[derive(Debug, Default)]
|
||||
struct Epoll {
|
||||
/// A map of EpollEventInterests registered under this epoll instance.
|
||||
/// Each entry is differentiated using FdId and file descriptor value.
|
||||
@ -18,11 +20,15 @@ struct Epoll {
|
||||
/// A map of EpollEventInstance that will be returned when `epoll_wait` is called.
|
||||
/// Similar to interest_list, the entry is also differentiated using FdId
|
||||
/// and file descriptor value.
|
||||
// This is an Rc because EpollInterest need to hold a reference to update
|
||||
// it.
|
||||
ready_list: Rc<ReadyList>,
|
||||
ready_list: ReadyList,
|
||||
/// A list of thread ids blocked on this epoll instance.
|
||||
thread_id: RefCell<Vec<ThreadId>>,
|
||||
blocked_tid: RefCell<Vec<ThreadId>>,
|
||||
}
|
||||
|
||||
impl VisitProvenance for Epoll {
|
||||
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
|
||||
// No provenance anywhere in this type.
|
||||
}
|
||||
}
|
||||
|
||||
/// EpollEventInstance contains information that will be returned by epoll_wait.
|
||||
@ -51,7 +57,7 @@ impl EpollEventInstance {
|
||||
/// see the man page:
|
||||
///
|
||||
/// <https://man7.org/linux/man-pages/man2/epoll_ctl.2.html>
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Debug)]
|
||||
pub struct EpollEventInterest {
|
||||
/// The file descriptor value of the file description registered.
|
||||
/// This is only used for ready_list, to inform userspace which FD triggered an event.
|
||||
@ -65,10 +71,10 @@ pub struct EpollEventInterest {
|
||||
/// but only u64 is supported for now.
|
||||
/// <https://man7.org/linux/man-pages/man3/epoll_event.3type.html>
|
||||
data: u64,
|
||||
/// Ready list of the epoll instance under which this EpollEventInterest is registered.
|
||||
ready_list: Rc<ReadyList>,
|
||||
/// The epoll file description that this EpollEventInterest is registered under.
|
||||
weak_epfd: WeakFileDescriptionRef,
|
||||
/// This is weak to avoid cycles, but an upgrade is always guaranteed to succeed
|
||||
/// because only the `Epoll` holds a strong ref to a `EpollEventInterest`.
|
||||
weak_epfd: WeakFileDescriptionRef<Epoll>,
|
||||
}
|
||||
|
||||
/// EpollReadyEvents reflects the readiness of a file description.
|
||||
@ -134,19 +140,13 @@ impl EpollReadyEvents {
|
||||
}
|
||||
}
|
||||
|
||||
impl Epoll {
|
||||
fn get_ready_list(&self) -> Rc<ReadyList> {
|
||||
Rc::clone(&self.ready_list)
|
||||
}
|
||||
}
|
||||
|
||||
impl FileDescription for Epoll {
|
||||
fn name(&self) -> &'static str {
|
||||
"epoll"
|
||||
}
|
||||
|
||||
fn close<'tcx>(
|
||||
self: Box<Self>,
|
||||
self,
|
||||
_communicate_allowed: bool,
|
||||
_ecx: &mut MiriInterpCx<'tcx>,
|
||||
) -> InterpResult<'tcx, io::Result<()>> {
|
||||
@ -271,17 +271,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
let Some(epfd) = this.machine.fds.get(epfd_value) else {
|
||||
return this.set_last_error_and_return_i32(LibcError("EBADF"));
|
||||
};
|
||||
let epoll_file_description = epfd
|
||||
let epfd = epfd
|
||||
.downcast::<Epoll>()
|
||||
.ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_ctl`"))?;
|
||||
|
||||
let mut interest_list = epoll_file_description.interest_list.borrow_mut();
|
||||
let ready_list = &epoll_file_description.ready_list;
|
||||
let mut interest_list = epfd.interest_list.borrow_mut();
|
||||
|
||||
let Some(fd_ref) = this.machine.fds.get(fd) else {
|
||||
return this.set_last_error_and_return_i32(LibcError("EBADF"));
|
||||
};
|
||||
let id = fd_ref.get_id();
|
||||
let id = fd_ref.id();
|
||||
|
||||
if op == epoll_ctl_add || op == epoll_ctl_mod {
|
||||
// Read event bitmask and data from epoll_event passed by caller.
|
||||
@ -337,30 +336,33 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
}
|
||||
}
|
||||
|
||||
// Create an epoll_interest.
|
||||
let interest = Rc::new(RefCell::new(EpollEventInterest {
|
||||
fd_num: fd,
|
||||
events,
|
||||
data,
|
||||
ready_list: Rc::clone(ready_list),
|
||||
weak_epfd: epfd.downgrade(),
|
||||
}));
|
||||
|
||||
if op == epoll_ctl_add {
|
||||
// Create an epoll_interest.
|
||||
let interest = Rc::new(RefCell::new(EpollEventInterest {
|
||||
fd_num: fd,
|
||||
events,
|
||||
data,
|
||||
weak_epfd: FileDescriptionRef::downgrade(&epfd),
|
||||
}));
|
||||
// Notification will be returned for current epfd if there is event in the file
|
||||
// descriptor we registered.
|
||||
check_and_update_one_event_interest(&fd_ref, &interest, id, this)?;
|
||||
|
||||
// Insert an epoll_interest to global epoll_interest list.
|
||||
this.machine.epoll_interests.insert_epoll_interest(id, Rc::downgrade(&interest));
|
||||
interest_list.insert(epoll_key, Rc::clone(&interest));
|
||||
interest_list.insert(epoll_key, interest);
|
||||
} else {
|
||||
// Directly modify the epoll_interest so the global epoll_event_interest table
|
||||
// will be updated too.
|
||||
let mut epoll_interest = interest_list.get_mut(&epoll_key).unwrap().borrow_mut();
|
||||
epoll_interest.events = events;
|
||||
epoll_interest.data = data;
|
||||
// Modify the existing interest.
|
||||
let epoll_interest = interest_list.get_mut(&epoll_key).unwrap();
|
||||
{
|
||||
let mut epoll_interest = epoll_interest.borrow_mut();
|
||||
epoll_interest.events = events;
|
||||
epoll_interest.data = data;
|
||||
}
|
||||
// Updating an FD interest triggers events.
|
||||
check_and_update_one_event_interest(&fd_ref, epoll_interest, id, this)?;
|
||||
}
|
||||
|
||||
// Notification will be returned for current epfd if there is event in the file
|
||||
// descriptor we registered.
|
||||
check_and_update_one_event_interest(&fd_ref, interest, id, this)?;
|
||||
interp_ok(Scalar::from_i32(0))
|
||||
} else if op == epoll_ctl_del {
|
||||
let epoll_key = (id, fd);
|
||||
@ -373,7 +375,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
drop(epoll_interest);
|
||||
|
||||
// Remove related epoll_interest from ready list.
|
||||
ready_list.mapping.borrow_mut().remove(&epoll_key);
|
||||
epfd.ready_list.mapping.borrow_mut().remove(&epoll_key);
|
||||
|
||||
// Remove dangling EpollEventInterest from its global table.
|
||||
// .unwrap() below should succeed because the file description id must have registered
|
||||
@ -452,24 +454,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
let Some(epfd) = this.machine.fds.get(epfd_value) else {
|
||||
return this.set_last_error_and_return(LibcError("EBADF"), dest);
|
||||
};
|
||||
// Create a weak ref of epfd and pass it to callback so we will make sure that epfd
|
||||
// is not close after the thread unblocks.
|
||||
let weak_epfd = epfd.downgrade();
|
||||
let Some(epfd) = epfd.downcast::<Epoll>() else {
|
||||
return this.set_last_error_and_return(LibcError("EBADF"), dest);
|
||||
};
|
||||
|
||||
// We just need to know if the ready list is empty and borrow the thread_ids out.
|
||||
// The whole logic is wrapped inside a block so we don't need to manually drop epfd later.
|
||||
let ready_list_empty;
|
||||
let mut thread_ids;
|
||||
{
|
||||
let epoll_file_description = epfd
|
||||
.downcast::<Epoll>()
|
||||
.ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_wait`"))?;
|
||||
ready_list_empty = epoll_file_description.ready_list.mapping.borrow().is_empty();
|
||||
thread_ids = epoll_file_description.thread_id.borrow_mut();
|
||||
}
|
||||
let ready_list_empty = epfd.ready_list.mapping.borrow().is_empty();
|
||||
if timeout == 0 || !ready_list_empty {
|
||||
// If the ready list is not empty, or the timeout is 0, we can return immediately.
|
||||
return_ready_list(epfd_value, weak_epfd, dest, &event, this)?;
|
||||
return_ready_list(&epfd, dest, &event, this)?;
|
||||
} else {
|
||||
// Blocking
|
||||
let timeout = match timeout {
|
||||
@ -484,34 +477,37 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
);
|
||||
}
|
||||
};
|
||||
thread_ids.push(this.active_thread());
|
||||
// Record this thread as blocked.
|
||||
epfd.blocked_tid.borrow_mut().push(this.active_thread());
|
||||
// And block it.
|
||||
let dest = dest.clone();
|
||||
// We keep a strong ref to the underlying `Epoll` to make sure it sticks around.
|
||||
// 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,
|
||||
timeout,
|
||||
callback!(
|
||||
@capture<'tcx> {
|
||||
epfd_value: i32,
|
||||
weak_epfd: WeakFileDescriptionRef,
|
||||
epfd: FileDescriptionRef<Epoll>,
|
||||
dest: MPlaceTy<'tcx>,
|
||||
event: MPlaceTy<'tcx>,
|
||||
}
|
||||
@unblock = |this| {
|
||||
return_ready_list(epfd_value, weak_epfd, &dest, &event, this)?;
|
||||
interp_ok(())
|
||||
}
|
||||
@timeout = |this| {
|
||||
// No notification after blocking timeout.
|
||||
let Some(epfd) = weak_epfd.upgrade() else {
|
||||
throw_unsup_format!("epoll FD {epfd_value} got closed while blocking.")
|
||||
};
|
||||
// Remove the current active thread_id from the blocked thread_id list.
|
||||
epfd.downcast::<Epoll>()
|
||||
.ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_wait`"))?
|
||||
.thread_id.borrow_mut()
|
||||
.retain(|&id| id != this.active_thread());
|
||||
this.write_int(0, &dest)?;
|
||||
interp_ok(())
|
||||
|this, unblock: UnblockKind| {
|
||||
match unblock {
|
||||
UnblockKind::Ready => {
|
||||
return_ready_list(&epfd, &dest, &event, this)?;
|
||||
interp_ok(())
|
||||
},
|
||||
UnblockKind::TimedOut => {
|
||||
// Remove the current active thread_id from the blocked thread_id list.
|
||||
epfd
|
||||
.blocked_tid.borrow_mut()
|
||||
.retain(|&id| id != this.active_thread());
|
||||
this.write_int(0, &dest)?;
|
||||
interp_ok(())
|
||||
},
|
||||
}
|
||||
}
|
||||
),
|
||||
);
|
||||
@ -528,21 +524,17 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
/// do not call this function when an FD didn't have anything happen to it!
|
||||
fn check_and_update_readiness(
|
||||
&mut self,
|
||||
fd_ref: &FileDescriptionRef,
|
||||
fd_ref: DynFileDescriptionRef,
|
||||
) -> InterpResult<'tcx, ()> {
|
||||
let this = self.eval_context_mut();
|
||||
let id = fd_ref.get_id();
|
||||
let id = fd_ref.id();
|
||||
let mut waiter = Vec::new();
|
||||
// Get a list of EpollEventInterest that is associated to a specific file description.
|
||||
if let Some(epoll_interests) = this.machine.epoll_interests.get_epoll_interest(id) {
|
||||
for weak_epoll_interest in epoll_interests {
|
||||
if let Some(epoll_interest) = weak_epoll_interest.upgrade() {
|
||||
let is_updated = check_and_update_one_event_interest(
|
||||
fd_ref,
|
||||
epoll_interest.clone(),
|
||||
id,
|
||||
this,
|
||||
)?;
|
||||
let is_updated =
|
||||
check_and_update_one_event_interest(&fd_ref, &epoll_interest, id, this)?;
|
||||
if is_updated {
|
||||
// Edge-triggered notification only notify one thread even if there are
|
||||
// multiple threads blocked on the same epfd.
|
||||
@ -553,10 +545,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
// holds a strong ref to epoll_interest.
|
||||
let epfd = epoll_interest.borrow().weak_epfd.upgrade().unwrap();
|
||||
// FIXME: We can randomly pick a thread to unblock.
|
||||
|
||||
let epoll = epfd.downcast::<Epoll>().unwrap();
|
||||
|
||||
if let Some(thread_id) = epoll.thread_id.borrow_mut().pop() {
|
||||
if let Some(thread_id) = epfd.blocked_tid.borrow_mut().pop() {
|
||||
waiter.push(thread_id);
|
||||
};
|
||||
}
|
||||
@ -595,14 +584,15 @@ fn ready_list_next(
|
||||
/// notification was added/updated. Unlike check_and_update_readiness, this function sends a
|
||||
/// notification to only one epoll instance.
|
||||
fn check_and_update_one_event_interest<'tcx>(
|
||||
fd_ref: &FileDescriptionRef,
|
||||
interest: Rc<RefCell<EpollEventInterest>>,
|
||||
fd_ref: &DynFileDescriptionRef,
|
||||
interest: &RefCell<EpollEventInterest>,
|
||||
id: FdId,
|
||||
ecx: &MiriInterpCx<'tcx>,
|
||||
) -> InterpResult<'tcx, bool> {
|
||||
// Get the bitmask of ready events for a file description.
|
||||
let ready_events_bitmask = fd_ref.as_unix().get_epoll_ready_events()?.get_event_bitmask(ecx);
|
||||
let epoll_event_interest = interest.borrow();
|
||||
let epfd = epoll_event_interest.weak_epfd.upgrade().unwrap();
|
||||
// This checks if any of the events specified in epoll_event_interest.events
|
||||
// match those in ready_events.
|
||||
let flags = epoll_event_interest.events & ready_events_bitmask;
|
||||
@ -610,7 +600,7 @@ fn check_and_update_one_event_interest<'tcx>(
|
||||
// insert an epoll_return to the ready list.
|
||||
if flags != 0 {
|
||||
let epoll_key = (id, epoll_event_interest.fd_num);
|
||||
let ready_list = &mut epoll_event_interest.ready_list.mapping.borrow_mut();
|
||||
let mut ready_list = epfd.ready_list.mapping.borrow_mut();
|
||||
let mut event_instance = EpollEventInstance::new(flags, epoll_event_interest.data);
|
||||
// If we are tracking data races, remember the current clock so we can sync with it later.
|
||||
ecx.release_clock(|clock| {
|
||||
@ -627,23 +617,12 @@ fn check_and_update_one_event_interest<'tcx>(
|
||||
/// Stores the ready list of the `epfd` epoll instance into `events` (which must be an array),
|
||||
/// and the number of returned events into `dest`.
|
||||
fn return_ready_list<'tcx>(
|
||||
epfd_value: i32,
|
||||
weak_epfd: WeakFileDescriptionRef,
|
||||
epfd: &FileDescriptionRef<Epoll>,
|
||||
dest: &MPlaceTy<'tcx>,
|
||||
events: &MPlaceTy<'tcx>,
|
||||
ecx: &mut MiriInterpCx<'tcx>,
|
||||
) -> InterpResult<'tcx> {
|
||||
let Some(epfd) = weak_epfd.upgrade() else {
|
||||
throw_unsup_format!("epoll FD {epfd_value} got closed while blocking.")
|
||||
};
|
||||
|
||||
let epoll_file_description = epfd
|
||||
.downcast::<Epoll>()
|
||||
.ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_wait`"))?;
|
||||
|
||||
let ready_list = epoll_file_description.get_ready_list();
|
||||
|
||||
let mut ready_list = ready_list.mapping.borrow_mut();
|
||||
let mut ready_list = epfd.ready_list.mapping.borrow_mut();
|
||||
let mut num_of_events: i32 = 0;
|
||||
let mut array_iter = ecx.project_array_fields(events)?;
|
||||
|
||||
|
@ -20,7 +20,7 @@ const MAX_COUNTER: u64 = u64::MAX - 1;
|
||||
///
|
||||
/// <https://man.netbsd.org/eventfd.2>
|
||||
#[derive(Debug)]
|
||||
struct Event {
|
||||
struct EventFd {
|
||||
/// The object contains an unsigned 64-bit integer (uint64_t) counter that is maintained by the
|
||||
/// kernel. This counter is initialized with the value specified in the argument initval.
|
||||
counter: Cell<u64>,
|
||||
@ -32,13 +32,13 @@ struct Event {
|
||||
blocked_write_tid: RefCell<Vec<ThreadId>>,
|
||||
}
|
||||
|
||||
impl FileDescription for Event {
|
||||
impl FileDescription for EventFd {
|
||||
fn name(&self) -> &'static str {
|
||||
"event"
|
||||
}
|
||||
|
||||
fn close<'tcx>(
|
||||
self: Box<Self>,
|
||||
self,
|
||||
_communicate_allowed: bool,
|
||||
_ecx: &mut MiriInterpCx<'tcx>,
|
||||
) -> InterpResult<'tcx, io::Result<()>> {
|
||||
@ -47,8 +47,7 @@ impl FileDescription for Event {
|
||||
|
||||
/// Read the counter in the buffer and return the counter if succeeded.
|
||||
fn read<'tcx>(
|
||||
&self,
|
||||
self_ref: &FileDescriptionRef,
|
||||
self: FileDescriptionRef<Self>,
|
||||
_communicate_allowed: bool,
|
||||
ptr: Pointer,
|
||||
len: usize,
|
||||
@ -62,11 +61,10 @@ impl FileDescription for Event {
|
||||
return ecx.set_last_error_and_return(ErrorKind::InvalidInput, dest);
|
||||
}
|
||||
|
||||
// eventfd read at the size of u64.
|
||||
// Turn the pointer into a place at the right type.
|
||||
let buf_place = ecx.ptr_to_mplace_unaligned(ptr, ty);
|
||||
|
||||
let weak_eventfd = self_ref.downgrade();
|
||||
eventfd_read(buf_place, dest, weak_eventfd, ecx)
|
||||
eventfd_read(buf_place, dest, self, ecx)
|
||||
}
|
||||
|
||||
/// A write call adds the 8-byte integer value supplied in
|
||||
@ -82,8 +80,7 @@ impl FileDescription for Event {
|
||||
/// supplied buffer is less than 8 bytes, or if an attempt is
|
||||
/// made to write the value 0xffffffffffffffff.
|
||||
fn write<'tcx>(
|
||||
&self,
|
||||
self_ref: &FileDescriptionRef,
|
||||
self: FileDescriptionRef<Self>,
|
||||
_communicate_allowed: bool,
|
||||
ptr: Pointer,
|
||||
len: usize,
|
||||
@ -97,18 +94,10 @@ impl FileDescription for Event {
|
||||
return ecx.set_last_error_and_return(ErrorKind::InvalidInput, dest);
|
||||
}
|
||||
|
||||
// Read the user-supplied value from the pointer.
|
||||
// Turn the pointer into a place at the right type.
|
||||
let buf_place = ecx.ptr_to_mplace_unaligned(ptr, ty);
|
||||
let num = ecx.read_scalar(&buf_place)?.to_u64()?;
|
||||
|
||||
// u64::MAX as input is invalid because the maximum value of counter is u64::MAX - 1.
|
||||
if num == u64::MAX {
|
||||
return ecx.set_last_error_and_return(ErrorKind::InvalidInput, dest);
|
||||
}
|
||||
// If the addition does not let the counter to exceed the maximum value, update the counter.
|
||||
// Else, block.
|
||||
let weak_eventfd = self_ref.downgrade();
|
||||
eventfd_write(num, buf_place, dest, weak_eventfd, ecx)
|
||||
eventfd_write(buf_place, dest, self, ecx)
|
||||
}
|
||||
|
||||
fn as_unix(&self) -> &dyn UnixFileDescription {
|
||||
@ -116,7 +105,7 @@ impl FileDescription for Event {
|
||||
}
|
||||
}
|
||||
|
||||
impl UnixFileDescription for Event {
|
||||
impl UnixFileDescription for EventFd {
|
||||
fn get_epoll_ready_events<'tcx>(&self) -> InterpResult<'tcx, EpollReadyEvents> {
|
||||
// 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.
|
||||
@ -178,7 +167,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
|
||||
let fds = &mut this.machine.fds;
|
||||
|
||||
let fd_value = fds.insert_new(Event {
|
||||
let fd_value = fds.insert_new(EventFd {
|
||||
counter: Cell::new(val.into()),
|
||||
is_nonblock,
|
||||
clock: RefCell::new(VClock::default()),
|
||||
@ -193,19 +182,17 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
/// Block thread if the value addition will exceed u64::MAX -1,
|
||||
/// else just add the user-supplied value to current counter.
|
||||
fn eventfd_write<'tcx>(
|
||||
num: u64,
|
||||
buf_place: MPlaceTy<'tcx>,
|
||||
dest: &MPlaceTy<'tcx>,
|
||||
weak_eventfd: WeakFileDescriptionRef,
|
||||
eventfd: FileDescriptionRef<EventFd>,
|
||||
ecx: &mut MiriInterpCx<'tcx>,
|
||||
) -> InterpResult<'tcx> {
|
||||
let Some(eventfd_ref) = weak_eventfd.upgrade() else {
|
||||
throw_unsup_format!("eventfd FD got closed while blocking.")
|
||||
};
|
||||
|
||||
// Since we pass the weak file description ref, it is guaranteed to be
|
||||
// an eventfd file description.
|
||||
let eventfd = eventfd_ref.downcast::<Event>().unwrap();
|
||||
// Figure out which value we should add.
|
||||
let num = ecx.read_scalar(&buf_place)?.to_u64()?;
|
||||
// u64::MAX as input is invalid because the maximum value of counter is u64::MAX - 1.
|
||||
if num == u64::MAX {
|
||||
return ecx.set_last_error_and_return(ErrorKind::InvalidInput, dest);
|
||||
}
|
||||
|
||||
match eventfd.counter.get().checked_add(num) {
|
||||
Some(new_count @ 0..=MAX_COUNTER) => {
|
||||
@ -217,10 +204,6 @@ fn eventfd_write<'tcx>(
|
||||
// Store new counter value.
|
||||
eventfd.counter.set(new_count);
|
||||
|
||||
// The state changed; we check and update the status of all supported event
|
||||
// types for current file description.
|
||||
ecx.check_and_update_readiness(&eventfd_ref)?;
|
||||
|
||||
// Unblock *all* threads previously blocked on `read`.
|
||||
// We need to take out the blocked thread ids and unblock them together,
|
||||
// because `unblock_threads` may block them again and end up re-adding the
|
||||
@ -231,6 +214,10 @@ fn eventfd_write<'tcx>(
|
||||
ecx.unblock_thread(thread_id, BlockReason::Eventfd)?;
|
||||
}
|
||||
|
||||
// The state changed; we check and update the status of all supported event
|
||||
// types for current file description.
|
||||
ecx.check_and_update_readiness(eventfd)?;
|
||||
|
||||
// Return how many bytes we consumed from the user-provided buffer.
|
||||
return ecx.write_int(buf_place.layout.size.bytes(), dest);
|
||||
}
|
||||
@ -244,6 +231,7 @@ fn eventfd_write<'tcx>(
|
||||
|
||||
eventfd.blocked_write_tid.borrow_mut().push(ecx.active_thread());
|
||||
|
||||
let weak_eventfd = FileDescriptionRef::downgrade(&eventfd);
|
||||
ecx.block_thread(
|
||||
BlockReason::Eventfd,
|
||||
None,
|
||||
@ -252,11 +240,14 @@ fn eventfd_write<'tcx>(
|
||||
num: u64,
|
||||
buf_place: MPlaceTy<'tcx>,
|
||||
dest: MPlaceTy<'tcx>,
|
||||
weak_eventfd: WeakFileDescriptionRef,
|
||||
weak_eventfd: WeakFileDescriptionRef<EventFd>,
|
||||
}
|
||||
@unblock = |this| {
|
||||
// When we get unblocked, try again.
|
||||
eventfd_write(num, buf_place, &dest, weak_eventfd, this)
|
||||
|this, unblock: UnblockKind| {
|
||||
assert_eq!(unblock, UnblockKind::Ready);
|
||||
// When we get unblocked, try again. We know the ref is still valid,
|
||||
// otherwise there couldn't be a `write` that unblocks us.
|
||||
let eventfd_ref = weak_eventfd.upgrade().unwrap();
|
||||
eventfd_write(buf_place, &dest, eventfd_ref, this)
|
||||
}
|
||||
),
|
||||
);
|
||||
@ -270,17 +261,9 @@ fn eventfd_write<'tcx>(
|
||||
fn eventfd_read<'tcx>(
|
||||
buf_place: MPlaceTy<'tcx>,
|
||||
dest: &MPlaceTy<'tcx>,
|
||||
weak_eventfd: WeakFileDescriptionRef,
|
||||
eventfd: FileDescriptionRef<EventFd>,
|
||||
ecx: &mut MiriInterpCx<'tcx>,
|
||||
) -> InterpResult<'tcx> {
|
||||
let Some(eventfd_ref) = weak_eventfd.upgrade() else {
|
||||
throw_unsup_format!("eventfd FD got closed while blocking.")
|
||||
};
|
||||
|
||||
// Since we pass the weak file description ref to the callback function, it is guaranteed to be
|
||||
// an eventfd file description.
|
||||
let eventfd = eventfd_ref.downcast::<Event>().unwrap();
|
||||
|
||||
// Set counter to 0, get old value.
|
||||
let counter = eventfd.counter.replace(0);
|
||||
|
||||
@ -293,6 +276,7 @@ fn eventfd_read<'tcx>(
|
||||
|
||||
eventfd.blocked_read_tid.borrow_mut().push(ecx.active_thread());
|
||||
|
||||
let weak_eventfd = FileDescriptionRef::downgrade(&eventfd);
|
||||
ecx.block_thread(
|
||||
BlockReason::Eventfd,
|
||||
None,
|
||||
@ -300,11 +284,14 @@ fn eventfd_read<'tcx>(
|
||||
@capture<'tcx> {
|
||||
buf_place: MPlaceTy<'tcx>,
|
||||
dest: MPlaceTy<'tcx>,
|
||||
weak_eventfd: WeakFileDescriptionRef,
|
||||
weak_eventfd: WeakFileDescriptionRef<EventFd>,
|
||||
}
|
||||
@unblock = |this| {
|
||||
// When we get unblocked, try again.
|
||||
eventfd_read(buf_place, &dest, weak_eventfd, this)
|
||||
|this, unblock: UnblockKind| {
|
||||
assert_eq!(unblock, UnblockKind::Ready);
|
||||
// When we get unblocked, try again. We know the ref is still valid,
|
||||
// otherwise there couldn't be a `write` that unblocks us.
|
||||
let eventfd_ref = weak_eventfd.upgrade().unwrap();
|
||||
eventfd_read(buf_place, &dest, eventfd_ref, this)
|
||||
}
|
||||
),
|
||||
);
|
||||
@ -315,10 +302,6 @@ fn eventfd_read<'tcx>(
|
||||
// Return old counter value into user-space buffer.
|
||||
ecx.write_int(counter, &buf_place)?;
|
||||
|
||||
// The state changed; we check and update the status of all supported event
|
||||
// types for current file description.
|
||||
ecx.check_and_update_readiness(&eventfd_ref)?;
|
||||
|
||||
// Unblock *all* threads previously blocked on `write`.
|
||||
// We need to take out the blocked thread ids and unblock them together,
|
||||
// because `unblock_threads` may block them again and end up re-adding the
|
||||
@ -329,6 +312,10 @@ fn eventfd_read<'tcx>(
|
||||
ecx.unblock_thread(thread_id, BlockReason::Eventfd)?;
|
||||
}
|
||||
|
||||
// The state changed; we check and update the status of all supported event
|
||||
// types for current file description.
|
||||
ecx.check_and_update_readiness(eventfd)?;
|
||||
|
||||
// Tell userspace how many bytes we put into the buffer.
|
||||
return ecx.write_int(buf_place.layout.size.bytes(), dest);
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ use rustc_span::Symbol;
|
||||
use rustc_target::callconv::{Conv, FnAbi};
|
||||
|
||||
use super::sync::EvalContextExt as _;
|
||||
use crate::helpers::check_min_arg_count;
|
||||
use crate::shims::unix::*;
|
||||
use crate::*;
|
||||
|
||||
@ -67,6 +68,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
let result = this.realpath(path, resolved_path)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
"ioctl" => {
|
||||
// `ioctl` is variadic. The argument count is checked based on the first argument
|
||||
// in `this.ioctl()`, so we do not use `check_shim` here.
|
||||
this.check_abi_and_shim_symbol_clash(abi, Conv::C, link_name)?;
|
||||
let result = this.ioctl(args)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
|
||||
// Environment related shims
|
||||
"_NSGetEnviron" => {
|
||||
@ -112,7 +120,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
this.check_no_isolation("`_NSGetExecutablePath`")?;
|
||||
|
||||
let buf_ptr = this.read_pointer(buf)?;
|
||||
let bufsize = this.deref_pointer(bufsize)?;
|
||||
let bufsize = this.deref_pointer_as(bufsize, this.machine.layouts.u32)?;
|
||||
|
||||
// Using the host current_exe is a bit off, but consistent with Linux
|
||||
// (where stdlib reads /proc/self/exe).
|
||||
@ -234,4 +242,26 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
|
||||
interp_ok(EmulateItemResult::NeedsReturn)
|
||||
}
|
||||
|
||||
fn ioctl(&mut self, args: &[OpTy<'tcx>]) -> InterpResult<'tcx, Scalar> {
|
||||
let this = self.eval_context_mut();
|
||||
|
||||
let fioclex = this.eval_libc_u64("FIOCLEX");
|
||||
|
||||
let [fd_num, cmd] = check_min_arg_count("ioctl", args)?;
|
||||
let fd_num = this.read_scalar(fd_num)?.to_i32()?;
|
||||
let cmd = this.read_scalar(cmd)?.to_u64()?;
|
||||
|
||||
if cmd == fioclex {
|
||||
// Since we don't support `exec`, this is a NOP. However, we want to
|
||||
// return EBADF if the FD is invalid.
|
||||
if this.machine.fds.is_fd_num(fd_num) {
|
||||
interp_ok(Scalar::from_i32(0))
|
||||
} else {
|
||||
this.set_last_error_and_return_i32(LibcError("EBADF"))
|
||||
}
|
||||
} else {
|
||||
throw_unsup_format!("ioctl: unsupported command {cmd:#x}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
None,
|
||||
callback!(
|
||||
@capture<'tcx> {}
|
||||
@unblock = |_this| {
|
||||
|_this, _unblock: UnblockKind| {
|
||||
panic!("we shouldn't wake up ever")
|
||||
}
|
||||
),
|
||||
|
@ -3,6 +3,8 @@ use rustc_span::Symbol;
|
||||
use rustc_target::callconv::{Conv, FnAbi};
|
||||
|
||||
use crate::shims::unix::foreign_items::EvalContextExt as _;
|
||||
use crate::shims::unix::linux_like::epoll::EvalContextExt as _;
|
||||
use crate::shims::unix::linux_like::eventfd::EvalContextExt as _;
|
||||
use crate::shims::unix::*;
|
||||
use crate::*;
|
||||
|
||||
@ -21,6 +23,32 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
) -> InterpResult<'tcx, EmulateItemResult> {
|
||||
let this = self.eval_context_mut();
|
||||
match link_name.as_str() {
|
||||
// epoll, eventfd (NOT available on Solaris!)
|
||||
"epoll_create1" => {
|
||||
this.assert_target_os("illumos", "epoll_create1");
|
||||
let [flag] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let result = this.epoll_create1(flag)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
"epoll_ctl" => {
|
||||
this.assert_target_os("illumos", "epoll_ctl");
|
||||
let [epfd, op, fd, event] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let result = this.epoll_ctl(epfd, op, fd, event)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
"epoll_wait" => {
|
||||
this.assert_target_os("illumos", "epoll_wait");
|
||||
let [epfd, events, maxevents, timeout] =
|
||||
this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
this.epoll_wait(epfd, events, maxevents, timeout, dest)?;
|
||||
}
|
||||
"eventfd" => {
|
||||
this.assert_target_os("illumos", "eventfd");
|
||||
let [val, flag] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let result = this.eventfd(val, flag)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
|
||||
// Threading
|
||||
"pthread_setname_np" => {
|
||||
let [thread, name] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
@ -78,6 +106,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
|
||||
// Sockets and pipes
|
||||
"__xnet_socketpair" => {
|
||||
let [domain, type_, protocol, sv] =
|
||||
this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let result = this.socketpair(domain, type_, protocol, sv)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
|
||||
// Miscellaneous
|
||||
"___errno" => {
|
||||
let [] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
|
@ -31,7 +31,7 @@ struct AnonSocket {
|
||||
/// The `AnonSocket` file descriptor that is our "peer", and that holds the buffer we are
|
||||
/// writing to. This is a weak reference because the other side may be closed before us; all
|
||||
/// future writes will then trigger EPIPE.
|
||||
peer_fd: OnceCell<WeakFileDescriptionRef>,
|
||||
peer_fd: OnceCell<WeakFileDescriptionRef<AnonSocket>>,
|
||||
/// Indicates whether the peer has lost data when the file description is closed.
|
||||
/// This flag is set to `true` if the peer's `readbuf` is non-empty at the time
|
||||
/// of closure.
|
||||
@ -58,7 +58,7 @@ impl Buffer {
|
||||
}
|
||||
|
||||
impl AnonSocket {
|
||||
fn peer_fd(&self) -> &WeakFileDescriptionRef {
|
||||
fn peer_fd(&self) -> &WeakFileDescriptionRef<AnonSocket> {
|
||||
self.peer_fd.get().unwrap()
|
||||
}
|
||||
}
|
||||
@ -69,7 +69,7 @@ impl FileDescription for AnonSocket {
|
||||
}
|
||||
|
||||
fn close<'tcx>(
|
||||
self: Box<Self>,
|
||||
self,
|
||||
_communicate_allowed: bool,
|
||||
ecx: &mut MiriInterpCx<'tcx>,
|
||||
) -> InterpResult<'tcx, io::Result<()>> {
|
||||
@ -78,80 +78,35 @@ impl FileDescription for AnonSocket {
|
||||
// notify the peer that data lost has happened in current file description.
|
||||
if let Some(readbuf) = &self.readbuf {
|
||||
if !readbuf.borrow().buf.is_empty() {
|
||||
peer_fd.downcast::<AnonSocket>().unwrap().peer_lost_data.set(true);
|
||||
peer_fd.peer_lost_data.set(true);
|
||||
}
|
||||
}
|
||||
// Notify peer fd that close has happened, since that can unblock reads and writes.
|
||||
ecx.check_and_update_readiness(&peer_fd)?;
|
||||
ecx.check_and_update_readiness(peer_fd)?;
|
||||
}
|
||||
interp_ok(Ok(()))
|
||||
}
|
||||
|
||||
fn read<'tcx>(
|
||||
&self,
|
||||
self_ref: &FileDescriptionRef,
|
||||
self: FileDescriptionRef<Self>,
|
||||
_communicate_allowed: bool,
|
||||
ptr: Pointer,
|
||||
len: usize,
|
||||
dest: &MPlaceTy<'tcx>,
|
||||
ecx: &mut MiriInterpCx<'tcx>,
|
||||
) -> InterpResult<'tcx> {
|
||||
// Always succeed on read size 0.
|
||||
if len == 0 {
|
||||
return ecx.return_read_success(ptr, &[], 0, dest);
|
||||
}
|
||||
|
||||
let Some(readbuf) = &self.readbuf else {
|
||||
// FIXME: This should return EBADF, but there's no nice way to do that as there's no
|
||||
// corresponding ErrorKind variant.
|
||||
throw_unsup_format!("reading from the write end of a pipe");
|
||||
};
|
||||
|
||||
if readbuf.borrow().buf.is_empty() && self.is_nonblock {
|
||||
// Non-blocking socketpair with writer and empty buffer.
|
||||
// https://linux.die.net/man/2/read
|
||||
// EAGAIN or EWOULDBLOCK can be returned for socket,
|
||||
// POSIX.1-2001 allows either error to be returned for this case.
|
||||
// Since there is no ErrorKind for EAGAIN, WouldBlock is used.
|
||||
return ecx.set_last_error_and_return(ErrorKind::WouldBlock, dest);
|
||||
}
|
||||
anonsocket_read(self_ref.downgrade(), len, ptr, dest.clone(), ecx)
|
||||
anonsocket_read(self, len, ptr, dest, ecx)
|
||||
}
|
||||
|
||||
fn write<'tcx>(
|
||||
&self,
|
||||
self_ref: &FileDescriptionRef,
|
||||
self: FileDescriptionRef<Self>,
|
||||
_communicate_allowed: bool,
|
||||
ptr: Pointer,
|
||||
len: usize,
|
||||
dest: &MPlaceTy<'tcx>,
|
||||
ecx: &mut MiriInterpCx<'tcx>,
|
||||
) -> InterpResult<'tcx> {
|
||||
// Always succeed on write size 0.
|
||||
// ("If count is zero and fd refers to a file other than a regular file, the results are not specified.")
|
||||
if len == 0 {
|
||||
return ecx.return_write_success(0, dest);
|
||||
}
|
||||
|
||||
// We are writing to our peer's readbuf.
|
||||
let Some(peer_fd) = self.peer_fd().upgrade() else {
|
||||
// If the upgrade from Weak to Rc fails, it indicates that all read ends have been
|
||||
// closed.
|
||||
return ecx.set_last_error_and_return(ErrorKind::BrokenPipe, dest);
|
||||
};
|
||||
|
||||
let Some(writebuf) = &peer_fd.downcast::<AnonSocket>().unwrap().readbuf else {
|
||||
// FIXME: This should return EBADF, but there's no nice way to do that as there's no
|
||||
// corresponding ErrorKind variant.
|
||||
throw_unsup_format!("writing to the reading end of a pipe");
|
||||
};
|
||||
let available_space =
|
||||
MAX_SOCKETPAIR_BUFFER_CAPACITY.strict_sub(writebuf.borrow().buf.len());
|
||||
if available_space == 0 && self.is_nonblock {
|
||||
// Non-blocking socketpair with a full buffer.
|
||||
return ecx.set_last_error_and_return(ErrorKind::WouldBlock, dest);
|
||||
}
|
||||
anonsocket_write(self_ref.downgrade(), ptr, len, dest.clone(), ecx)
|
||||
anonsocket_write(self, ptr, len, dest, ecx)
|
||||
}
|
||||
|
||||
fn as_unix(&self) -> &dyn UnixFileDescription {
|
||||
@ -161,50 +116,64 @@ impl FileDescription for AnonSocket {
|
||||
|
||||
/// Write to AnonSocket based on the space available and return the written byte size.
|
||||
fn anonsocket_write<'tcx>(
|
||||
weak_self_ref: WeakFileDescriptionRef,
|
||||
self_ref: FileDescriptionRef<AnonSocket>,
|
||||
ptr: Pointer,
|
||||
len: usize,
|
||||
dest: MPlaceTy<'tcx>,
|
||||
dest: &MPlaceTy<'tcx>,
|
||||
ecx: &mut MiriInterpCx<'tcx>,
|
||||
) -> InterpResult<'tcx> {
|
||||
let Some(self_ref) = weak_self_ref.upgrade() else {
|
||||
// FIXME: We should raise a deadlock error if the self_ref upgrade failed.
|
||||
throw_unsup_format!("This will be a deadlock error in future")
|
||||
};
|
||||
let self_anonsocket = self_ref.downcast::<AnonSocket>().unwrap();
|
||||
let Some(peer_fd) = self_anonsocket.peer_fd().upgrade() else {
|
||||
// Always succeed on write size 0.
|
||||
// ("If count is zero and fd refers to a file other than a regular file, the results are not specified.")
|
||||
if len == 0 {
|
||||
return ecx.return_write_success(0, dest);
|
||||
}
|
||||
|
||||
// We are writing to our peer's readbuf.
|
||||
let Some(peer_fd) = self_ref.peer_fd().upgrade() else {
|
||||
// If the upgrade from Weak to Rc fails, it indicates that all read ends have been
|
||||
// closed.
|
||||
return ecx.set_last_error_and_return(ErrorKind::BrokenPipe, &dest);
|
||||
};
|
||||
let Some(writebuf) = &peer_fd.downcast::<AnonSocket>().unwrap().readbuf else {
|
||||
// FIXME: This should return EBADF, but there's no nice way to do that as there's no
|
||||
// corresponding ErrorKind variant.
|
||||
throw_unsup_format!("writing to the reading end of a pipe")
|
||||
// closed. It is an error to write even if there would be space.
|
||||
return ecx.set_last_error_and_return(ErrorKind::BrokenPipe, dest);
|
||||
};
|
||||
|
||||
let Some(writebuf) = &peer_fd.readbuf else {
|
||||
// Writing to the read end of a pipe.
|
||||
return ecx.set_last_error_and_return(IoError::LibcError("EBADF"), dest);
|
||||
};
|
||||
|
||||
// Let's see if we can write.
|
||||
let available_space = MAX_SOCKETPAIR_BUFFER_CAPACITY.strict_sub(writebuf.borrow().buf.len());
|
||||
|
||||
if available_space == 0 {
|
||||
// Blocking socketpair with a full buffer.
|
||||
let dest = dest.clone();
|
||||
self_anonsocket.blocked_write_tid.borrow_mut().push(ecx.active_thread());
|
||||
ecx.block_thread(
|
||||
BlockReason::UnnamedSocket,
|
||||
None,
|
||||
callback!(
|
||||
@capture<'tcx> {
|
||||
weak_self_ref: WeakFileDescriptionRef,
|
||||
ptr: Pointer,
|
||||
len: usize,
|
||||
dest: MPlaceTy<'tcx>,
|
||||
}
|
||||
@unblock = |this| {
|
||||
anonsocket_write(weak_self_ref, ptr, len, dest, this)
|
||||
}
|
||||
),
|
||||
);
|
||||
if self_ref.is_nonblock {
|
||||
// Non-blocking socketpair with a full buffer.
|
||||
return ecx.set_last_error_and_return(ErrorKind::WouldBlock, dest);
|
||||
} else {
|
||||
self_ref.blocked_write_tid.borrow_mut().push(ecx.active_thread());
|
||||
// Blocking socketpair with a full buffer.
|
||||
// Block the current thread; only keep a weak ref for this.
|
||||
let weak_self_ref = FileDescriptionRef::downgrade(&self_ref);
|
||||
let dest = dest.clone();
|
||||
ecx.block_thread(
|
||||
BlockReason::UnnamedSocket,
|
||||
None,
|
||||
callback!(
|
||||
@capture<'tcx> {
|
||||
weak_self_ref: WeakFileDescriptionRef<AnonSocket>,
|
||||
ptr: Pointer,
|
||||
len: usize,
|
||||
dest: MPlaceTy<'tcx>,
|
||||
}
|
||||
|this, unblock: UnblockKind| {
|
||||
assert_eq!(unblock, UnblockKind::Ready);
|
||||
// If we got unblocked, then our peer successfully upgraded its weak
|
||||
// ref to us. That means we can also upgrade our weak ref.
|
||||
let self_ref = weak_self_ref.upgrade().unwrap();
|
||||
anonsocket_write(self_ref, ptr, len, &dest, this)
|
||||
}
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// There is space to write!
|
||||
let mut writebuf = writebuf.borrow_mut();
|
||||
// Remember this clock so `read` can synchronize with us.
|
||||
ecx.release_clock(|clock| {
|
||||
@ -218,68 +187,80 @@ fn anonsocket_write<'tcx>(
|
||||
// Need to stop accessing peer_fd so that it can be notified.
|
||||
drop(writebuf);
|
||||
|
||||
// Notification should be provided for peer fd as it became readable.
|
||||
// The kernel does this even if the fd was already readable before, so we follow suit.
|
||||
ecx.check_and_update_readiness(&peer_fd)?;
|
||||
let peer_anonsocket = peer_fd.downcast::<AnonSocket>().unwrap();
|
||||
// Unblock all threads that are currently blocked on peer_fd's read.
|
||||
let waiting_threads = std::mem::take(&mut *peer_anonsocket.blocked_read_tid.borrow_mut());
|
||||
let waiting_threads = std::mem::take(&mut *peer_fd.blocked_read_tid.borrow_mut());
|
||||
// FIXME: We can randomize the order of unblocking.
|
||||
for thread_id in waiting_threads {
|
||||
ecx.unblock_thread(thread_id, BlockReason::UnnamedSocket)?;
|
||||
}
|
||||
// Notification should be provided for peer fd as it became readable.
|
||||
// The kernel does this even if the fd was already readable before, so we follow suit.
|
||||
ecx.check_and_update_readiness(peer_fd)?;
|
||||
|
||||
return ecx.return_write_success(actual_write_size, &dest);
|
||||
return ecx.return_write_success(actual_write_size, dest);
|
||||
}
|
||||
interp_ok(())
|
||||
}
|
||||
|
||||
/// Read from AnonSocket and return the number of bytes read.
|
||||
fn anonsocket_read<'tcx>(
|
||||
weak_self_ref: WeakFileDescriptionRef,
|
||||
self_ref: FileDescriptionRef<AnonSocket>,
|
||||
len: usize,
|
||||
ptr: Pointer,
|
||||
dest: MPlaceTy<'tcx>,
|
||||
dest: &MPlaceTy<'tcx>,
|
||||
ecx: &mut MiriInterpCx<'tcx>,
|
||||
) -> InterpResult<'tcx> {
|
||||
let Some(self_ref) = weak_self_ref.upgrade() else {
|
||||
// FIXME: We should raise a deadlock error if the self_ref upgrade failed.
|
||||
throw_unsup_format!("This will be a deadlock error in future")
|
||||
};
|
||||
let self_anonsocket = self_ref.downcast::<AnonSocket>().unwrap();
|
||||
// Always succeed on read size 0.
|
||||
if len == 0 {
|
||||
return ecx.return_read_success(ptr, &[], 0, dest);
|
||||
}
|
||||
|
||||
let Some(readbuf) = &self_anonsocket.readbuf else {
|
||||
let Some(readbuf) = &self_ref.readbuf else {
|
||||
// FIXME: This should return EBADF, but there's no nice way to do that as there's no
|
||||
// corresponding ErrorKind variant.
|
||||
throw_unsup_format!("reading from the write end of a pipe")
|
||||
};
|
||||
|
||||
if readbuf.borrow_mut().buf.is_empty() {
|
||||
if self_anonsocket.peer_fd().upgrade().is_none() {
|
||||
if self_ref.peer_fd().upgrade().is_none() {
|
||||
// Socketpair with no peer and empty buffer.
|
||||
// 0 bytes successfully read indicates end-of-file.
|
||||
return ecx.return_read_success(ptr, &[], 0, &dest);
|
||||
return ecx.return_read_success(ptr, &[], 0, dest);
|
||||
} else if self_ref.is_nonblock {
|
||||
// Non-blocking socketpair with writer and empty buffer.
|
||||
// https://linux.die.net/man/2/read
|
||||
// EAGAIN or EWOULDBLOCK can be returned for socket,
|
||||
// POSIX.1-2001 allows either error to be returned for this case.
|
||||
// Since there is no ErrorKind for EAGAIN, WouldBlock is used.
|
||||
return ecx.set_last_error_and_return(ErrorKind::WouldBlock, dest);
|
||||
} else {
|
||||
self_ref.blocked_read_tid.borrow_mut().push(ecx.active_thread());
|
||||
// Blocking socketpair with writer and empty buffer.
|
||||
let weak_self_ref = weak_self_ref.clone();
|
||||
self_anonsocket.blocked_read_tid.borrow_mut().push(ecx.active_thread());
|
||||
// Block the current thread; only keep a weak ref for this.
|
||||
let weak_self_ref = FileDescriptionRef::downgrade(&self_ref);
|
||||
let dest = dest.clone();
|
||||
ecx.block_thread(
|
||||
BlockReason::UnnamedSocket,
|
||||
None,
|
||||
callback!(
|
||||
@capture<'tcx> {
|
||||
weak_self_ref: WeakFileDescriptionRef,
|
||||
weak_self_ref: WeakFileDescriptionRef<AnonSocket>,
|
||||
len: usize,
|
||||
ptr: Pointer,
|
||||
dest: MPlaceTy<'tcx>,
|
||||
}
|
||||
@unblock = |this| {
|
||||
anonsocket_read(weak_self_ref, len, ptr, dest, this)
|
||||
|this, unblock: UnblockKind| {
|
||||
assert_eq!(unblock, UnblockKind::Ready);
|
||||
// If we got unblocked, then our peer successfully upgraded its weak
|
||||
// ref to us. That means we can also upgrade our weak ref.
|
||||
let self_ref = weak_self_ref.upgrade().unwrap();
|
||||
anonsocket_read(self_ref, len, ptr, &dest, this)
|
||||
}
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// There's data to be read!
|
||||
let mut bytes = vec![0; len];
|
||||
let mut readbuf = readbuf.borrow_mut();
|
||||
// Synchronize with all previous writes to this buffer.
|
||||
@ -301,19 +282,18 @@ fn anonsocket_read<'tcx>(
|
||||
// don't know what that *certain number* is, we will provide a notification every time
|
||||
// a read is successful. This might result in our epoll emulation providing more
|
||||
// notifications than the real system.
|
||||
if let Some(peer_fd) = self_anonsocket.peer_fd().upgrade() {
|
||||
ecx.check_and_update_readiness(&peer_fd)?;
|
||||
let peer_anonsocket = peer_fd.downcast::<AnonSocket>().unwrap();
|
||||
if let Some(peer_fd) = self_ref.peer_fd().upgrade() {
|
||||
// Unblock all threads that are currently blocked on peer_fd's write.
|
||||
let waiting_threads =
|
||||
std::mem::take(&mut *peer_anonsocket.blocked_write_tid.borrow_mut());
|
||||
let waiting_threads = std::mem::take(&mut *peer_fd.blocked_write_tid.borrow_mut());
|
||||
// FIXME: We can randomize the order of unblocking.
|
||||
for thread_id in waiting_threads {
|
||||
ecx.unblock_thread(thread_id, BlockReason::UnnamedSocket)?;
|
||||
}
|
||||
// Notify epoll waiters.
|
||||
ecx.check_and_update_readiness(peer_fd)?;
|
||||
};
|
||||
|
||||
return ecx.return_read_success(ptr, &bytes, actual_read_size, &dest);
|
||||
return ecx.return_read_success(ptr, &bytes, actual_read_size, dest);
|
||||
}
|
||||
interp_ok(())
|
||||
}
|
||||
@ -337,7 +317,7 @@ impl UnixFileDescription for AnonSocket {
|
||||
|
||||
// Check if is writable.
|
||||
if let Some(peer_fd) = self.peer_fd().upgrade() {
|
||||
if let Some(writebuf) = &peer_fd.downcast::<AnonSocket>().unwrap().readbuf {
|
||||
if let Some(writebuf) = &peer_fd.readbuf {
|
||||
let data_size = writebuf.borrow().buf.len();
|
||||
let available_space = MAX_SOCKETPAIR_BUFFER_CAPACITY.strict_sub(data_size);
|
||||
if available_space != 0 {
|
||||
@ -443,8 +423,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
});
|
||||
|
||||
// Make the file descriptions point to each other.
|
||||
fd0.downcast::<AnonSocket>().unwrap().peer_fd.set(fd1.downgrade()).unwrap();
|
||||
fd1.downcast::<AnonSocket>().unwrap().peer_fd.set(fd0.downgrade()).unwrap();
|
||||
fd0.peer_fd.set(FileDescriptionRef::downgrade(&fd1)).unwrap();
|
||||
fd1.peer_fd.set(FileDescriptionRef::downgrade(&fd0)).unwrap();
|
||||
|
||||
// Insert the file description to the fd table, generating the file descriptors.
|
||||
let sv0 = fds.insert(fd0);
|
||||
@ -511,8 +491,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
});
|
||||
|
||||
// Make the file descriptions point to each other.
|
||||
fd0.downcast::<AnonSocket>().unwrap().peer_fd.set(fd1.downgrade()).unwrap();
|
||||
fd1.downcast::<AnonSocket>().unwrap().peer_fd.set(fd0.downgrade()).unwrap();
|
||||
fd0.peer_fd.set(FileDescriptionRef::downgrade(&fd1)).unwrap();
|
||||
fd1.peer_fd.set(FileDescriptionRef::downgrade(&fd0)).unwrap();
|
||||
|
||||
// Insert the file description to the fd table, generating the file descriptors.
|
||||
let pipefd0 = fds.insert(fd0);
|
||||
|
@ -97,7 +97,7 @@ impl Handle {
|
||||
|
||||
// packs the data into the lower `data_size` bits
|
||||
// and packs the discriminant right above the data
|
||||
discriminant << data_size | data
|
||||
(discriminant << data_size) | data
|
||||
}
|
||||
|
||||
fn new(discriminant: u32, data: u32) -> Option<Self> {
|
||||
|
@ -111,7 +111,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
pending_place: MPlaceTy<'tcx>,
|
||||
dest: MPlaceTy<'tcx>,
|
||||
}
|
||||
@unblock = |this| {
|
||||
|this, unblock: UnblockKind| {
|
||||
assert_eq!(unblock, UnblockKind::Ready);
|
||||
let ret = this.init_once_try_begin(id, &pending_place, &dest)?;
|
||||
assert!(ret, "we were woken up but init_once_try_begin still failed");
|
||||
interp_ok(())
|
||||
|
@ -1,5 +1,4 @@
|
||||
//@ignore-target: windows # only very limited libc on Windows
|
||||
//@ignore-target: apple # `sched_setaffinity` is not supported on macOS
|
||||
//@only-target: linux # these are Linux-specific APIs
|
||||
//@compile-flags: -Zmiri-disable-isolation -Zmiri-num-cpus=4
|
||||
|
||||
fn main() {
|
||||
|
@ -1,4 +1,4 @@
|
||||
//@only-target: linux
|
||||
//@only-target: linux android illumos
|
||||
//~^ERROR: deadlocked
|
||||
//~^^ERROR: deadlocked
|
||||
//@compile-flags: -Zmiri-preemption-rate=0
|
||||
|
@ -1,4 +1,4 @@
|
||||
//@only-target: linux
|
||||
//@only-target: linux android illumos
|
||||
//~^ERROR: deadlocked
|
||||
//~^^ERROR: deadlocked
|
||||
//@compile-flags: -Zmiri-preemption-rate=0
|
||||
|
@ -2,7 +2,7 @@
|
||||
//! and we only read one of them, we do not synchronize with the other events
|
||||
//! and therefore still report a data race for things that need to see the second event
|
||||
//! to be considered synchronized.
|
||||
//@only-target: linux android
|
||||
//@only-target: linux android illumos
|
||||
// ensure deterministic schedule
|
||||
//@compile-flags: -Zmiri-preemption-rate=0
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
//@compile-flags: -Zmiri-preemption-rate=0
|
||||
//~^ERROR: deadlocked
|
||||
//~^^ERROR: deadlocked
|
||||
//@only-target: linux
|
||||
//@only-target: linux android illumos
|
||||
//@error-in-other-file: deadlock
|
||||
|
||||
use std::convert::TryInto;
|
||||
|
@ -1,4 +1,4 @@
|
||||
//@only-target: linux
|
||||
//@only-target: linux android illumos
|
||||
|
||||
// This is a test for registering unsupported fd with epoll.
|
||||
// Register epoll fd with epoll is allowed in real system, but we do not support this.
|
||||
|
@ -1,9 +1,8 @@
|
||||
//@ignore-target: windows # No `memrchr` on Windows
|
||||
//@ignore-target: apple # No `memrchr` on some apple targets
|
||||
//@only-target: linux # `memrchr` is a GNU extension
|
||||
|
||||
use std::ptr;
|
||||
|
||||
// null is explicitly called out as UB in the C docs.
|
||||
// null is explicitly called out as UB in the C docs for `memchr`.
|
||||
fn main() {
|
||||
unsafe {
|
||||
libc::memrchr(ptr::null(), 0, 0); //~ERROR: null pointer
|
||||
|
@ -0,0 +1,37 @@
|
||||
//! This is a regression test for <https://github.com/rust-lang/miri/issues/3947>: we had some
|
||||
//! faulty logic around `release_clock` that led to this code not reporting a data race.
|
||||
//~^^ERROR: deadlock
|
||||
//@ignore-target: windows # no libc socketpair on Windows
|
||||
//@compile-flags: -Zmiri-preemption-rate=0 -Zmiri-address-reuse-rate=0
|
||||
//@error-in-other-file: deadlock
|
||||
use std::thread;
|
||||
|
||||
fn main() {
|
||||
let mut fds = [-1, -1];
|
||||
let res = unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
|
||||
assert_eq!(res, 0);
|
||||
|
||||
let thread1 = thread::spawn(move || {
|
||||
let mut buf: [u8; 1] = [0; 1];
|
||||
let _res: i32 = unsafe {
|
||||
libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) //~ERROR: deadlock
|
||||
.try_into()
|
||||
.unwrap()
|
||||
};
|
||||
});
|
||||
let thread2 = thread::spawn(move || {
|
||||
// Close the FD that the other thread is blocked on.
|
||||
unsafe { libc::close(fds[1]) };
|
||||
});
|
||||
|
||||
// Run the other threads.
|
||||
thread::yield_now();
|
||||
|
||||
// When they are both done, continue here.
|
||||
let data = "a".as_bytes().as_ptr();
|
||||
let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 1) };
|
||||
assert_eq!(res, -1);
|
||||
|
||||
thread1.join().unwrap();
|
||||
thread2.join().unwrap();
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
error: deadlock: the evaluated program deadlocked
|
||||
--> RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
|
||||
|
|
||||
LL | let ret = unsafe { libc::pthread_join(id, ptr::null_mut()) };
|
||||
| ^ the evaluated program deadlocked
|
||||
|
|
||||
= note: BACKTRACE:
|
||||
= note: inside `std::sys::pal::PLATFORM::thread::Thread::join` at RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
|
||||
= note: inside `std::thread::JoinInner::<'_, ()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC
|
||||
= note: inside `std::thread::JoinHandle::<()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC
|
||||
note: inside `main`
|
||||
--> tests/fail-dep/libc/socketpair-close-while-blocked.rs:LL:CC
|
||||
|
|
||||
LL | thread1.join().unwrap();
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
||||
error: deadlock: the evaluated program deadlocked
|
||||
--> tests/fail-dep/libc/socketpair-close-while-blocked.rs:LL:CC
|
||||
|
|
||||
LL | libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t)
|
||||
| ^ the evaluated program deadlocked
|
||||
|
|
||||
= note: BACKTRACE on thread `unnamed-ID`:
|
||||
= note: inside closure at tests/fail-dep/libc/socketpair-close-while-blocked.rs:LL:CC
|
||||
|
||||
error: deadlock: the evaluated program deadlocked
|
||||
|
|
||||
= note: the evaluated program deadlocked
|
||||
= note: (no span available)
|
||||
= note: BACKTRACE on thread `unnamed-ID`:
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to 3 previous errors
|
||||
|
@ -1,5 +1,4 @@
|
||||
//@ignore-target: windows # only very limited libc on Windows
|
||||
//@ignore-target: apple # `sched_{g, s}etaffinity` are not supported on macOS
|
||||
//@only-target: linux # these are Linux-specific APIs
|
||||
//@compile-flags: -Zmiri-disable-isolation -Zmiri-num-cpus=4
|
||||
#![feature(io_error_more)]
|
||||
#![feature(pointer_is_aligned_to)]
|
||||
|
@ -1,4 +1,4 @@
|
||||
//@only-target: linux android
|
||||
//@only-target: linux android illumos
|
||||
// test_epoll_block_then_unblock and test_epoll_race depend on a deterministic schedule.
|
||||
//@compile-flags: -Zmiri-preemption-rate=0
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
//@only-target: linux android
|
||||
//@only-target: linux android illumos
|
||||
|
||||
use std::convert::TryInto;
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
//@only-target: linux android
|
||||
//@only-target: linux android illumos
|
||||
// test_race, test_blocking_read and test_blocking_write depend on a deterministic schedule.
|
||||
//@compile-flags: -Zmiri-preemption-rate=0
|
||||
|
||||
@ -10,7 +10,10 @@ use std::thread;
|
||||
fn main() {
|
||||
test_read_write();
|
||||
test_race();
|
||||
|
||||
#[cfg(not(target_os = "illumos"))]
|
||||
test_syscall();
|
||||
|
||||
test_blocking_read();
|
||||
test_blocking_write();
|
||||
test_two_threads_blocked_on_eventfd();
|
||||
@ -115,6 +118,8 @@ fn test_race() {
|
||||
}
|
||||
|
||||
// This is a test for calling eventfd2 through a syscall.
|
||||
// Illumos supports eventfd, but it has no entry to call it through syscall.
|
||||
#[cfg(not(target_os = "illumos"))]
|
||||
fn test_syscall() {
|
||||
let initval = 0 as libc::c_uint;
|
||||
let flags = (libc::EFD_CLOEXEC | libc::EFD_NONBLOCK) as libc::c_int;
|
||||
|
@ -38,6 +38,8 @@ fn main() {
|
||||
test_isatty();
|
||||
test_read_and_uninit();
|
||||
test_nofollow_not_symlink();
|
||||
#[cfg(target_os = "macos")]
|
||||
test_ioctl();
|
||||
}
|
||||
|
||||
fn test_file_open_unix_allow_two_args() {
|
||||
@ -431,3 +433,21 @@ fn test_nofollow_not_symlink() {
|
||||
let ret = unsafe { libc::open(cpath.as_ptr(), libc::O_NOFOLLOW | libc::O_CLOEXEC) };
|
||||
assert!(ret >= 0);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn test_ioctl() {
|
||||
let path = utils::prepare_with_content("miri_test_libc_ioctl.txt", &[]);
|
||||
|
||||
let mut name = path.into_os_string();
|
||||
name.push("\0");
|
||||
let name_ptr = name.as_bytes().as_ptr().cast::<libc::c_char>();
|
||||
unsafe {
|
||||
// 100 surely is an invalid FD.
|
||||
assert_eq!(libc::ioctl(100, libc::FIOCLEX), -1);
|
||||
let errno = std::io::Error::last_os_error().raw_os_error().unwrap();
|
||||
assert_eq!(errno, libc::EBADF);
|
||||
|
||||
let fd = libc::open(name_ptr, libc::O_RDONLY);
|
||||
assert_eq!(libc::ioctl(fd, libc::FIOCLEX), 0);
|
||||
}
|
||||
}
|
||||
|
@ -29,12 +29,13 @@ fn main() {
|
||||
|
||||
fn set_thread_name(name: &CStr) -> i32 {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(any(target_os = "linux", target_os = "illumos", target_os = "solaris"))] {
|
||||
if #[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "freebsd",
|
||||
target_os = "illumos",
|
||||
target_os = "solaris"
|
||||
))] {
|
||||
unsafe { libc::pthread_setname_np(libc::pthread_self(), name.as_ptr().cast()) }
|
||||
} else if #[cfg(target_os = "freebsd")] {
|
||||
// pthread_set_name_np does not return anything
|
||||
unsafe { libc::pthread_set_name_np(libc::pthread_self(), name.as_ptr().cast()) };
|
||||
0
|
||||
} else if #[cfg(target_os = "macos")] {
|
||||
unsafe { libc::pthread_setname_np(name.as_ptr().cast()) }
|
||||
} else {
|
||||
@ -47,6 +48,7 @@ fn main() {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "freebsd",
|
||||
target_os = "illumos",
|
||||
target_os = "solaris",
|
||||
target_os = "macos"
|
||||
@ -54,12 +56,6 @@ fn main() {
|
||||
unsafe {
|
||||
libc::pthread_getname_np(libc::pthread_self(), name.as_mut_ptr().cast(), name.len())
|
||||
}
|
||||
} else if #[cfg(target_os = "freebsd")] {
|
||||
// pthread_get_name_np does not return anything
|
||||
unsafe {
|
||||
libc::pthread_get_name_np(libc::pthread_self(), name.as_mut_ptr().cast(), name.len())
|
||||
};
|
||||
0
|
||||
} else {
|
||||
compile_error!("get_thread_name not supported for this OS")
|
||||
}
|
||||
@ -201,27 +197,25 @@ fn main() {
|
||||
.unwrap();
|
||||
|
||||
// Now set the name for a non-existing thread and verify error codes.
|
||||
// (FreeBSD doesn't return an error code.)
|
||||
#[cfg(not(target_os = "freebsd"))]
|
||||
{
|
||||
let invalid_thread = 0xdeadbeef;
|
||||
let error = {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(target_os = "linux")] {
|
||||
libc::ENOENT
|
||||
} else {
|
||||
libc::ESRCH
|
||||
}
|
||||
let invalid_thread = 0xdeadbeef;
|
||||
let error = {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(target_os = "linux")] {
|
||||
libc::ENOENT
|
||||
} else {
|
||||
libc::ESRCH
|
||||
}
|
||||
};
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
// macOS has no `setname` function accepting a thread id as the first argument.
|
||||
let res = unsafe { libc::pthread_setname_np(invalid_thread, [0].as_ptr()) };
|
||||
assert_eq!(res, error);
|
||||
}
|
||||
let mut buf = [0; 64];
|
||||
let res = unsafe { libc::pthread_getname_np(invalid_thread, buf.as_mut_ptr(), buf.len()) };
|
||||
};
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
// macOS has no `setname` function accepting a thread id as the first argument.
|
||||
let res = unsafe { libc::pthread_setname_np(invalid_thread, [0].as_ptr()) };
|
||||
assert_eq!(res, error);
|
||||
}
|
||||
|
||||
let mut buf = [0; 64];
|
||||
let res = unsafe { libc::pthread_getname_np(invalid_thread, buf.as_mut_ptr(), buf.len()) };
|
||||
assert_eq!(res, error);
|
||||
}
|
||||
|
13
src/tools/miri/tests/pass/shims/pipe.rs
Normal file
13
src/tools/miri/tests/pass/shims/pipe.rs
Normal file
@ -0,0 +1,13 @@
|
||||
//@ignore-target: windows
|
||||
|
||||
#![feature(anonymous_pipe)]
|
||||
|
||||
use std::io::{Read, Write, pipe};
|
||||
|
||||
fn main() {
|
||||
let (mut ping_rx, mut ping_tx) = pipe().unwrap();
|
||||
ping_tx.write(b"hello").unwrap();
|
||||
let mut buf: [u8; 5] = [0; 5];
|
||||
ping_rx.read(&mut buf).unwrap();
|
||||
assert_eq!(&buf, "hello".as_bytes());
|
||||
}
|
@ -1,3 +1,6 @@
|
||||
## See <https://forge.rust-lang.org/triagebot/index.html> for documentation
|
||||
## of these options.
|
||||
|
||||
[relabel]
|
||||
allow-unauthenticated = [
|
||||
"A-*",
|
||||
@ -30,5 +33,10 @@ remove_labels = ["S-waiting-on-author"]
|
||||
# Those labels are added when PR author requests a review from an assignee
|
||||
add_labels = ["S-waiting-on-review"]
|
||||
|
||||
[merge-conflicts]
|
||||
remove = []
|
||||
add = ["S-waiting-on-author"]
|
||||
unless = ["S-blocked", "S-waiting-on-team", "S-waiting-on-review"]
|
||||
|
||||
# Automatically close and reopen PRs made by bots to run CI on them
|
||||
[bot-pull-requests]
|
||||
|
Loading…
Reference in New Issue
Block a user