Rollup merge of #141383 - RalfJung:miri-sync, r=RalfJung
Miri subtree update r? `@ghost`
This commit is contained in:
commit
ee1768c2a3
49 changed files with 736 additions and 201 deletions
|
|
@ -3116,9 +3116,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rustc-build-sysroot"
|
||||
version = "0.5.5"
|
||||
version = "0.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb332121f7845c6bd016f9655cf22f03c2999df936694b624a88669a78667d98"
|
||||
checksum = "10edc2e4393515193bd766e2f6c050b0536a68e56f2b6d56c07ababfdc114ff0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"rustc_version",
|
||||
|
|
|
|||
|
|
@ -218,7 +218,7 @@ degree documented below):
|
|||
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 the entire test suite.
|
||||
- `freebsd`: maintained by @YohDeadfall. Supports `std::env` and parts of `std::{thread, fs}`, but not `std::sync`.
|
||||
- `freebsd`: maintained by @YohDeadfall and @LorrensP-2158466. Supports the entire test suite.
|
||||
- `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.
|
||||
|
|
|
|||
|
|
@ -208,9 +208,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rustc-build-sysroot"
|
||||
version = "0.5.4"
|
||||
version = "0.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6d984a9db43148467059309bd1e5ad577085162f695d9fe2cf3543aeb25cd38"
|
||||
checksum = "10edc2e4393515193bd766e2f6c050b0536a68e56f2b6d56c07ababfdc114ff0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"rustc_version",
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ directories = "6"
|
|||
rustc_version = "0.4"
|
||||
serde_json = "1.0.40"
|
||||
cargo_metadata = "0.19"
|
||||
rustc-build-sysroot = "0.5.4"
|
||||
rustc-build-sysroot = "0.5.7"
|
||||
|
||||
# Enable some feature flags that dev-dependencies need but dependencies
|
||||
# do not. This makes `./miri install` after `./miri build` faster.
|
||||
|
|
|
|||
|
|
@ -156,16 +156,17 @@ case $HOST_TARGET in
|
|||
MANY_SEEDS=64 TEST_TARGET=i686-pc-windows-gnu run_tests
|
||||
MANY_SEEDS=64 TEST_TARGET=x86_64-pc-windows-msvc CARGO_MIRI_ENV=1 run_tests
|
||||
# Extra tier 2
|
||||
TEST_TARGET=arm-unknown-linux-gnueabi run_tests
|
||||
TEST_TARGET=s390x-unknown-linux-gnu run_tests # big-endian architecture of choice
|
||||
MANY_SEEDS=16 TEST_TARGET=arm-unknown-linux-gnueabi run_tests
|
||||
MANY_SEEDS=16 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
|
||||
MANY_SEEDS=16 TEST_TARGET=mips-unknown-linux-gnu run_tests # a 32bit big-endian target, and also a target without 64bit atomics
|
||||
MANY_SEEDS=16 TEST_TARGET=x86_64-unknown-illumos run_tests
|
||||
MANY_SEEDS=16 TEST_TARGET=x86_64-pc-solaris run_tests
|
||||
MANY_SEEDS=16 TEST_TARGET=x86_64-unknown-freebsd run_tests
|
||||
MANY_SEEDS=16 TEST_TARGET=i686-unknown-freebsd 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 thread sync concurrency fs libc-pipe
|
||||
TEST_TARGET=i686-unknown-freebsd run_tests_minimal $BASIC $UNIX time hashmap random thread sync concurrency fs libc-pipe
|
||||
TEST_TARGET=aarch64-linux-android run_tests_minimal $BASIC $UNIX time hashmap random thread sync concurrency 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
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
ac17c3486c6fdfbb0c3c18b99f3d8dfbff625d29
|
||||
2b96ddca1272960623e41829439df8dae82d20af
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
AllocKind::Dead => unreachable!(),
|
||||
};
|
||||
// We don't have to expose this pointer yet, we do that in `prepare_for_native_call`.
|
||||
return interp_ok(base_ptr.addr().try_into().unwrap());
|
||||
return interp_ok(base_ptr.addr().to_u64());
|
||||
}
|
||||
// We are not in native lib mode, so we control the addresses ourselves.
|
||||
if let Some((reuse_addr, clock)) = global_state.reuse.take_addr(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use rand::Rng;
|
|||
use rustc_abi::{Align, Size};
|
||||
|
||||
use crate::concurrency::VClock;
|
||||
use crate::helpers::ToUsize as _;
|
||||
use crate::{MemoryKind, MiriConfig, ThreadId};
|
||||
|
||||
const MAX_POOL_SIZE: usize = 64;
|
||||
|
|
@ -46,7 +47,7 @@ impl ReusePool {
|
|||
}
|
||||
|
||||
fn subpool(&mut self, align: Align) -> &mut Vec<(u64, Size, ThreadId, VClock)> {
|
||||
let pool_idx: usize = align.bytes().trailing_zeros().try_into().unwrap();
|
||||
let pool_idx: usize = align.bytes().trailing_zeros().to_usize();
|
||||
if self.pool.len() <= pool_idx {
|
||||
self.pool.resize(pool_idx + 1, Vec::new());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ use std::{alloc, slice};
|
|||
use rustc_abi::{Align, Size};
|
||||
use rustc_middle::mir::interpret::AllocBytes;
|
||||
|
||||
use crate::helpers::ToU64 as _;
|
||||
|
||||
/// Allocation bytes that explicitly handle the layout of the data they're storing.
|
||||
/// This is necessary to interface with native code that accesses the program store in Miri.
|
||||
#[derive(Debug)]
|
||||
|
|
@ -21,7 +23,7 @@ pub struct MiriAllocBytes {
|
|||
impl Clone for MiriAllocBytes {
|
||||
fn clone(&self) -> Self {
|
||||
let bytes: Cow<'_, [u8]> = Cow::Borrowed(self);
|
||||
let align = Align::from_bytes(self.layout.align().try_into().unwrap()).unwrap();
|
||||
let align = Align::from_bytes(self.layout.align().to_u64()).unwrap();
|
||||
MiriAllocBytes::from_bytes(bytes, align)
|
||||
}
|
||||
}
|
||||
|
|
@ -90,7 +92,7 @@ impl AllocBytes for MiriAllocBytes {
|
|||
let align = align.bytes();
|
||||
// SAFETY: `alloc_fn` will only be used with `size != 0`.
|
||||
let alloc_fn = |layout| unsafe { alloc::alloc(layout) };
|
||||
let alloc_bytes = MiriAllocBytes::alloc_with(size.try_into().unwrap(), align, alloc_fn)
|
||||
let alloc_bytes = MiriAllocBytes::alloc_with(size.to_u64(), align, alloc_fn)
|
||||
.unwrap_or_else(|()| {
|
||||
panic!("Miri ran out of memory: cannot create allocation of {size} bytes")
|
||||
});
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ use std::mem;
|
|||
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
|
||||
use crate::helpers::ToUsize;
|
||||
|
||||
/// Intermediate key between a UniKeyMap and a UniValMap.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct UniIndex {
|
||||
|
|
@ -158,7 +160,7 @@ where
|
|||
impl<V> UniValMap<V> {
|
||||
/// Whether this index has an associated value.
|
||||
pub fn contains_idx(&self, idx: UniIndex) -> bool {
|
||||
self.data.get(idx.idx as usize).and_then(Option::as_ref).is_some()
|
||||
self.data.get(idx.idx.to_usize()).and_then(Option::as_ref).is_some()
|
||||
}
|
||||
|
||||
/// Reserve enough space to insert the value at the right index.
|
||||
|
|
@ -174,29 +176,29 @@ impl<V> UniValMap<V> {
|
|||
|
||||
/// Assign a value to the index. Permanently overwrites any previous value.
|
||||
pub fn insert(&mut self, idx: UniIndex, val: V) {
|
||||
self.extend_to_length(idx.idx as usize + 1);
|
||||
self.data[idx.idx as usize] = Some(val)
|
||||
self.extend_to_length(idx.idx.to_usize() + 1);
|
||||
self.data[idx.idx.to_usize()] = Some(val)
|
||||
}
|
||||
|
||||
/// Get the value at this index, if it exists.
|
||||
pub fn get(&self, idx: UniIndex) -> Option<&V> {
|
||||
self.data.get(idx.idx as usize).and_then(Option::as_ref)
|
||||
self.data.get(idx.idx.to_usize()).and_then(Option::as_ref)
|
||||
}
|
||||
|
||||
/// Get the value at this index mutably, if it exists.
|
||||
pub fn get_mut(&mut self, idx: UniIndex) -> Option<&mut V> {
|
||||
self.data.get_mut(idx.idx as usize).and_then(Option::as_mut)
|
||||
self.data.get_mut(idx.idx.to_usize()).and_then(Option::as_mut)
|
||||
}
|
||||
|
||||
/// Delete any value associated with this index.
|
||||
/// Returns None if the value was not present, otherwise
|
||||
/// returns the previously stored value.
|
||||
pub fn remove(&mut self, idx: UniIndex) -> Option<V> {
|
||||
if idx.idx as usize >= self.data.len() {
|
||||
if idx.idx.to_usize() >= self.data.len() {
|
||||
return None;
|
||||
}
|
||||
let mut res = None;
|
||||
mem::swap(&mut res, &mut self.data[idx.idx as usize]);
|
||||
mem::swap(&mut res, &mut self.data[idx.idx.to_usize()]);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
|
@ -209,8 +211,8 @@ pub struct UniEntry<'a, V> {
|
|||
impl<'a, V> UniValMap<V> {
|
||||
/// Get a wrapper around a mutable access to the value corresponding to `idx`.
|
||||
pub fn entry(&'a mut self, idx: UniIndex) -> UniEntry<'a, V> {
|
||||
self.extend_to_length(idx.idx as usize + 1);
|
||||
UniEntry { inner: &mut self.data[idx.idx as usize] }
|
||||
self.extend_to_length(idx.idx.to_usize() + 1);
|
||||
UniEntry { inner: &mut self.data[idx.idx.to_usize()] }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ impl CpuAffinityMask {
|
|||
let mut this = Self([0; Self::CPU_MASK_BYTES]);
|
||||
|
||||
// the default affinity mask includes only the available CPUs
|
||||
for i in 0..cpu_count as usize {
|
||||
for i in 0..cpu_count.to_usize() {
|
||||
this.set(cx, i);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use rustc_span::{DUMMY_SP, Span, SpanData};
|
|||
use smallvec::SmallVec;
|
||||
|
||||
use super::data_race::NaReadType;
|
||||
use crate::helpers::ToUsize;
|
||||
|
||||
/// A vector clock index, this is associated with a thread id
|
||||
/// but in some cases one vector index may be shared with
|
||||
|
|
@ -157,7 +158,7 @@ impl VClock {
|
|||
|
||||
#[inline]
|
||||
pub(super) fn index_mut(&mut self, index: VectorIdx) -> &mut VTimestamp {
|
||||
self.0.as_mut_slice().get_mut(index.to_u32() as usize).unwrap()
|
||||
self.0.as_mut_slice().get_mut(index.to_u32().to_usize()).unwrap()
|
||||
}
|
||||
|
||||
/// Get a mutable slice to the internal vector with minimum `min_len`
|
||||
|
|
@ -420,7 +421,7 @@ impl Index<VectorIdx> for VClock {
|
|||
|
||||
#[inline]
|
||||
fn index(&self, index: VectorIdx) -> &VTimestamp {
|
||||
self.as_slice().get(index.to_u32() as usize).unwrap_or(&VTimestamp::ZERO)
|
||||
self.as_slice().get(index.to_u32().to_usize()).unwrap_or(&VTimestamp::ZERO)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1412,3 +1412,26 @@ pub(crate) fn windows_check_buffer_size((success, len): (bool, u64)) -> u32 {
|
|||
u32::try_from(len).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// We don't support 16-bit systems, so let's have ergonomic conversion from `u32` to `usize`.
|
||||
pub trait ToUsize {
|
||||
fn to_usize(self) -> usize;
|
||||
}
|
||||
|
||||
impl ToUsize for u32 {
|
||||
fn to_usize(self) -> usize {
|
||||
self.try_into().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Similarly, a maximum address size of `u64` is assumed widely here, so let's have ergonomic
|
||||
/// converion from `usize` to `u64`.
|
||||
pub trait ToU64 {
|
||||
fn to_u64(self) -> u64;
|
||||
}
|
||||
|
||||
impl ToU64 for usize {
|
||||
fn to_u64(self) -> u64 {
|
||||
self.try_into().unwrap()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -634,7 +634,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
let index_len = index.len();
|
||||
|
||||
assert_eq!(left_len, right_len);
|
||||
assert_eq!(index_len as u64, dest_len);
|
||||
assert_eq!(u64::try_from(index_len).unwrap(), dest_len);
|
||||
|
||||
for i in 0..dest_len {
|
||||
let src_index: u64 =
|
||||
|
|
|
|||
|
|
@ -41,14 +41,7 @@
|
|||
rustc::potential_query_instability,
|
||||
rustc::untranslatable_diagnostic,
|
||||
)]
|
||||
#![warn(
|
||||
rust_2018_idioms,
|
||||
unqualified_local_imports,
|
||||
clippy::cast_possible_wrap, // unsigned -> signed
|
||||
clippy::cast_sign_loss, // signed -> unsigned
|
||||
clippy::cast_lossless,
|
||||
clippy::cast_possible_truncation,
|
||||
)]
|
||||
#![warn(rust_2018_idioms, unqualified_local_imports, clippy::as_conversions)]
|
||||
// Needed for rustdoc from bootstrap (with `-Znormalize-docs`).
|
||||
#![recursion_limit = "256"]
|
||||
|
||||
|
|
@ -140,7 +133,7 @@ pub use crate::eval::{
|
|||
AlignmentCheck, BacktraceStyle, IsolatedOp, MiriConfig, MiriEntryFnType, RejectOpWith,
|
||||
ValidationMode, create_ecx, eval_entry,
|
||||
};
|
||||
pub use crate::helpers::{AccessKind, EvalContextExt as _};
|
||||
pub use crate::helpers::{AccessKind, EvalContextExt as _, ToU64 as _, ToUsize as _};
|
||||
pub use crate::intrinsics::EvalContextExt as _;
|
||||
pub use crate::machine::{
|
||||
AllocExtra, DynMachineCallback, FrameExtra, MachineCallback, MemoryKind, MiriInterpCx,
|
||||
|
|
|
|||
|
|
@ -544,9 +544,6 @@ pub struct MiriMachine<'tcx> {
|
|||
/// Failure rate of compare_exchange_weak, between 0.0 and 1.0
|
||||
pub(crate) cmpxchg_weak_failure_rate: f64,
|
||||
|
||||
/// Corresponds to -Zmiri-mute-stdout-stderr and doesn't write the output but acts as if it succeeded.
|
||||
pub(crate) mute_stdout_stderr: bool,
|
||||
|
||||
/// The probability of the active thread being preempted at the end of each basic block.
|
||||
pub(crate) preemption_rate: f64,
|
||||
|
||||
|
|
@ -722,7 +719,6 @@ impl<'tcx> MiriMachine<'tcx> {
|
|||
track_alloc_accesses: config.track_alloc_accesses,
|
||||
check_alignment: config.check_alignment,
|
||||
cmpxchg_weak_failure_rate: config.cmpxchg_weak_failure_rate,
|
||||
mute_stdout_stderr: config.mute_stdout_stderr,
|
||||
preemption_rate: config.preemption_rate,
|
||||
report_progress: config.report_progress,
|
||||
basic_block_count: 0,
|
||||
|
|
@ -925,7 +921,6 @@ impl VisitProvenance for MiriMachine<'_> {
|
|||
track_alloc_accesses: _,
|
||||
check_alignment: _,
|
||||
cmpxchg_weak_failure_rate: _,
|
||||
mute_stdout_stderr: _,
|
||||
preemption_rate: _,
|
||||
report_progress: _,
|
||||
basic_block_count: _,
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
|
||||
let frame_count = this.active_thread_stack().len();
|
||||
|
||||
this.write_scalar(Scalar::from_target_usize(frame_count.try_into().unwrap(), this), dest)
|
||||
this.write_scalar(Scalar::from_target_usize(frame_count.to_u64(), this), dest)
|
||||
}
|
||||
|
||||
fn handle_miri_get_backtrace(
|
||||
|
|
@ -70,7 +70,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
}
|
||||
1 =>
|
||||
for (i, ptr) in ptrs.into_iter().enumerate() {
|
||||
let offset = ptr_layout.size.checked_mul(i.try_into().unwrap(), this).unwrap();
|
||||
let offset = ptr_layout.size.checked_mul(i.to_u64(), this).unwrap();
|
||||
|
||||
let op_place = buf_place.offset(offset, ptr_layout, this)?;
|
||||
|
||||
|
|
@ -158,11 +158,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
}
|
||||
1 => {
|
||||
this.write_scalar(
|
||||
Scalar::from_target_usize(name.len().try_into().unwrap(), this),
|
||||
Scalar::from_target_usize(name.len().to_u64(), this),
|
||||
&this.project_field(dest, 0)?,
|
||||
)?;
|
||||
this.write_scalar(
|
||||
Scalar::from_target_usize(filename.len().try_into().unwrap(), this),
|
||||
Scalar::from_target_usize(filename.len().to_u64(), this),
|
||||
&this.project_field(dest, 1)?,
|
||||
)?;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -135,7 +135,10 @@ pub trait FileDescription: std::fmt::Debug + FileDescriptionExt {
|
|||
|
||||
/// 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.
|
||||
///
|
||||
/// When the read is done, `finish` will be called. Note that `read` itself may return before
|
||||
/// that happens! Everything that should happen "after" the `read` needs to happen inside
|
||||
/// `finish`.
|
||||
fn read<'tcx>(
|
||||
self: FileDescriptionRef<Self>,
|
||||
_communicate_allowed: bool,
|
||||
|
|
@ -149,7 +152,10 @@ pub trait FileDescription: std::fmt::Debug + FileDescriptionExt {
|
|||
|
||||
/// Writes as much as possible from the given buffer `ptr`.
|
||||
/// `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.
|
||||
///
|
||||
/// When the write is done, `finish` will be called. Note that `write` itself may return before
|
||||
/// that happens! Everything that should happen "after" the `write` needs to happen inside
|
||||
/// `finish`.
|
||||
fn write<'tcx>(
|
||||
self: FileDescriptionRef<Self>,
|
||||
_communicate_allowed: bool,
|
||||
|
|
|
|||
|
|
@ -639,7 +639,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
let val = this.read_scalar(val)?.to_i32()?;
|
||||
let num = this.read_target_usize(num)?;
|
||||
// The docs say val is "interpreted as unsigned char".
|
||||
#[expect(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
|
||||
#[expect(clippy::as_conversions)]
|
||||
let val = val as u8;
|
||||
|
||||
// C requires that this must always be a valid pointer (C18 §7.1.4).
|
||||
|
|
@ -665,7 +665,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
let val = this.read_scalar(val)?.to_i32()?;
|
||||
let num = this.read_target_usize(num)?;
|
||||
// The docs say val is "interpreted as unsigned char".
|
||||
#[expect(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
|
||||
#[expect(clippy::as_conversions)]
|
||||
let val = val as u8;
|
||||
|
||||
// C requires that this must always be a valid pointer (C18 §7.1.4).
|
||||
|
|
@ -676,7 +676,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
.iter()
|
||||
.position(|&c| c == val);
|
||||
if let Some(idx) = idx {
|
||||
let new_ptr = ptr.wrapping_offset(Size::from_bytes(idx as u64), this);
|
||||
let new_ptr = ptr.wrapping_offset(Size::from_bytes(idx), this);
|
||||
this.write_pointer(new_ptr, dest)?;
|
||||
} else {
|
||||
this.write_null(dest)?;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use std::io;
|
||||
use std::io::ErrorKind;
|
||||
|
||||
use crate::*;
|
||||
|
||||
|
|
@ -13,6 +14,29 @@ pub enum IoError {
|
|||
}
|
||||
pub use self::IoError::*;
|
||||
|
||||
impl IoError {
|
||||
pub(crate) fn into_ntstatus(self) -> i32 {
|
||||
let raw = match self {
|
||||
HostError(e) =>
|
||||
match e.kind() {
|
||||
// STATUS_MEDIA_WRITE_PROTECTED
|
||||
ErrorKind::ReadOnlyFilesystem => 0xC00000A2u32,
|
||||
// STATUS_FILE_INVALID
|
||||
ErrorKind::InvalidInput => 0xC0000098,
|
||||
// STATUS_DISK_FULL
|
||||
ErrorKind::QuotaExceeded => 0xC000007F,
|
||||
// STATUS_ACCESS_DENIED
|
||||
ErrorKind::PermissionDenied => 0xC0000022,
|
||||
// For the default error code we arbitrarily pick 0xC0000185, STATUS_IO_DEVICE_ERROR.
|
||||
_ => 0xC0000185,
|
||||
},
|
||||
// For the default error code we arbitrarily pick 0xC0000185, STATUS_IO_DEVICE_ERROR.
|
||||
_ => 0xC0000185,
|
||||
};
|
||||
raw.cast_signed()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for IoError {
|
||||
fn from(value: io::Error) -> Self {
|
||||
IoError::HostError(value)
|
||||
|
|
|
|||
|
|
@ -93,14 +93,10 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
let this = self.eval_context_mut();
|
||||
// Try getting the function from the shared library.
|
||||
let (lib, lib_path) = this.machine.native_lib.as_ref().unwrap();
|
||||
let func: libloading::Symbol<'_, unsafe extern "C" fn()> = unsafe {
|
||||
match lib.get(link_name.as_str().as_bytes()) {
|
||||
Ok(x) => x,
|
||||
Err(_) => {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
};
|
||||
let func: libloading::Symbol<'_, unsafe extern "C" fn()> =
|
||||
unsafe { lib.get(link_name.as_str().as_bytes()).ok()? };
|
||||
#[expect(clippy::as_conversions)] // fn-ptr to raw-ptr cast needs `as`.
|
||||
let fn_ptr = *func.deref() as *mut std::ffi::c_void;
|
||||
|
||||
// FIXME: this is a hack!
|
||||
// The `libloading` crate will automatically load system libraries like `libc`.
|
||||
|
|
@ -115,7 +111,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
// using the `libc` crate where this interface is public.
|
||||
let mut info = std::mem::MaybeUninit::<libc::Dl_info>::zeroed();
|
||||
unsafe {
|
||||
if libc::dladdr(*func.deref() as *const _, info.as_mut_ptr()) != 0 {
|
||||
if libc::dladdr(fn_ptr, info.as_mut_ptr()) != 0 {
|
||||
let info = info.assume_init();
|
||||
#[cfg(target_os = "cygwin")]
|
||||
let fname_ptr = info.dli_fname.as_ptr();
|
||||
|
|
@ -129,8 +125,9 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return a pointer to the function.
|
||||
Some(CodePtr(*func.deref() as *mut _))
|
||||
Some(CodePtr(fn_ptr))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use crate::helpers::check_min_vararg_count;
|
|||
use crate::shims::unix::thread::{EvalContextExt as _, ThreadNameResult};
|
||||
use crate::*;
|
||||
|
||||
const TASK_COMM_LEN: usize = 16;
|
||||
const TASK_COMM_LEN: u64 = 16;
|
||||
|
||||
pub fn prctl<'tcx>(
|
||||
ecx: &mut MiriInterpCx<'tcx>,
|
||||
|
|
@ -38,7 +38,7 @@ pub fn prctl<'tcx>(
|
|||
let [name] = check_min_vararg_count("prctl(PR_GET_NAME, ...)", varargs)?;
|
||||
let name = ecx.read_scalar(name)?;
|
||||
let thread = ecx.pthread_self()?;
|
||||
let len = Scalar::from_target_usize(TASK_COMM_LEN as u64, ecx);
|
||||
let len = Scalar::from_target_usize(TASK_COMM_LEN, ecx);
|
||||
ecx.check_ptr_access(
|
||||
name.to_pointer(ecx)?,
|
||||
Size::from_bytes(TASK_COMM_LEN),
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
// Threading
|
||||
"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.
|
||||
let max_len = u64::MAX; // FreeBSD does not seem to have a limit.
|
||||
let res = match this.pthread_setname_np(
|
||||
this.read_scalar(thread)?,
|
||||
this.read_scalar(name)?,
|
||||
|
|
@ -56,6 +56,70 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
this.write_scalar(res, dest)?;
|
||||
}
|
||||
|
||||
"cpuset_getaffinity" => {
|
||||
// The "same" kind of api as `sched_getaffinity` but more fine grained control for FreeBSD specifically.
|
||||
let [level, which, id, set_size, mask] =
|
||||
this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
|
||||
let level = this.read_scalar(level)?.to_i32()?;
|
||||
let which = this.read_scalar(which)?.to_i32()?;
|
||||
let id = this.read_scalar(id)?.to_i64()?;
|
||||
let set_size = this.read_target_usize(set_size)?; // measured in bytes
|
||||
let mask = this.read_pointer(mask)?;
|
||||
|
||||
let _level_root = this.eval_libc_i32("CPU_LEVEL_ROOT");
|
||||
let _level_cpuset = this.eval_libc_i32("CPU_LEVEL_CPUSET");
|
||||
let level_which = this.eval_libc_i32("CPU_LEVEL_WHICH");
|
||||
|
||||
let _which_tid = this.eval_libc_i32("CPU_WHICH_TID");
|
||||
let which_pid = this.eval_libc_i32("CPU_WHICH_PID");
|
||||
let _which_jail = this.eval_libc_i32("CPU_WHICH_JAIL");
|
||||
let _which_cpuset = this.eval_libc_i32("CPU_WHICH_CPUSET");
|
||||
let _which_irq = this.eval_libc_i32("CPU_WHICH_IRQ");
|
||||
|
||||
// For sched_getaffinity, the current process is identified by -1.
|
||||
// TODO: Use gettid? I'm (LorrensP-2158466) not that familiar with this api .
|
||||
let id = match id {
|
||||
-1 => this.active_thread(),
|
||||
_ =>
|
||||
throw_unsup_format!(
|
||||
"`cpuset_getaffinity` is only supported with a pid of -1 (indicating the current thread)"
|
||||
),
|
||||
};
|
||||
|
||||
if this.ptr_is_null(mask)? {
|
||||
this.set_last_error_and_return(LibcError("EFAULT"), dest)?;
|
||||
}
|
||||
// We only support CPU_LEVEL_WHICH and CPU_WHICH_PID for now.
|
||||
// This is the bare minimum to make the tests pass.
|
||||
else if level != level_which || which != which_pid {
|
||||
throw_unsup_format!(
|
||||
"`cpuset_getaffinity` is only supported with `level` set to CPU_LEVEL_WHICH and `which` set to CPU_WHICH_PID."
|
||||
);
|
||||
} else if let Some(cpuset) = this.machine.thread_cpu_affinity.get(&id) {
|
||||
// `cpusetsize` must be large enough to contain the entire CPU mask.
|
||||
// FreeBSD only uses `cpusetsize` to verify that it's sufficient for the kernel's CPU mask.
|
||||
// If it's too small, the syscall returns ERANGE.
|
||||
// If it's large enough, copying the kernel mask to user space is safe, regardless of the actual size.
|
||||
// See https://github.com/freebsd/freebsd-src/blob/909aa6781340f8c0b4ae01c6366bf1556ee2d1be/sys/kern/kern_cpuset.c#L1985
|
||||
if set_size < u64::from(this.machine.num_cpus).div_ceil(8) {
|
||||
this.set_last_error_and_return(LibcError("ERANGE"), dest)?;
|
||||
} else {
|
||||
let cpuset = cpuset.clone();
|
||||
let byte_count =
|
||||
Ord::min(cpuset.as_slice().len(), set_size.try_into().unwrap());
|
||||
this.write_bytes_ptr(
|
||||
mask,
|
||||
cpuset.as_slice()[..byte_count].iter().copied(),
|
||||
)?;
|
||||
this.write_null(dest)?;
|
||||
}
|
||||
} else {
|
||||
// `id` is always that of the active thread, so this is currently unreachable.
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
|
||||
// Synchronization primitives
|
||||
"_umtx_op" => {
|
||||
let [obj, op, val, uaddr, uaddr2] =
|
||||
|
|
|
|||
|
|
@ -121,13 +121,13 @@ impl UnixFileDescription for FileHandle {
|
|||
use std::os::windows::io::AsRawHandle;
|
||||
|
||||
use windows_sys::Win32::Foundation::{
|
||||
ERROR_IO_PENDING, ERROR_LOCK_VIOLATION, FALSE, HANDLE, TRUE,
|
||||
ERROR_IO_PENDING, ERROR_LOCK_VIOLATION, FALSE, TRUE,
|
||||
};
|
||||
use windows_sys::Win32::Storage::FileSystem::{
|
||||
LOCKFILE_EXCLUSIVE_LOCK, LOCKFILE_FAIL_IMMEDIATELY, LockFileEx, UnlockFile,
|
||||
};
|
||||
|
||||
let fh = self.file.as_raw_handle() as HANDLE;
|
||||
let fh = self.file.as_raw_handle();
|
||||
|
||||
use FlockOp::*;
|
||||
let (ret, lock_nb) = match op {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ use crate::*;
|
|||
// The documentation of glibc complains that the kernel never exposes
|
||||
// TASK_COMM_LEN through the headers, so it's assumed to always be 16 bytes
|
||||
// long including a null terminator.
|
||||
const TASK_COMM_LEN: usize = 16;
|
||||
const TASK_COMM_LEN: u64 = 16;
|
||||
|
||||
pub fn is_dyn_sym(name: &str) -> bool {
|
||||
matches!(name, "statx")
|
||||
|
|
@ -96,7 +96,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
// In case of glibc, the length of the output buffer must
|
||||
// be not shorter than TASK_COMM_LEN.
|
||||
let len = this.read_scalar(len)?;
|
||||
let res = if len.to_target_usize(this)? >= TASK_COMM_LEN as u64 {
|
||||
let res = if len.to_target_usize(this)? >= TASK_COMM_LEN {
|
||||
match this.pthread_getname_np(
|
||||
this.read_scalar(thread)?,
|
||||
this.read_scalar(name)?,
|
||||
|
|
|
|||
|
|
@ -186,7 +186,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
let res = match this.pthread_setname_np(
|
||||
thread,
|
||||
this.read_scalar(name)?,
|
||||
this.eval_libc("MAXTHREADNAMESIZE").to_target_usize(this)?.try_into().unwrap(),
|
||||
this.eval_libc("MAXTHREADNAMESIZE").to_target_usize(this)?,
|
||||
/* truncate */ false,
|
||||
)? {
|
||||
ThreadNameResult::Ok => Scalar::from_u32(0),
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
&mut self,
|
||||
thread: Scalar,
|
||||
name: Scalar,
|
||||
name_max_len: usize,
|
||||
name_max_len: u64,
|
||||
truncate: bool,
|
||||
) -> InterpResult<'tcx, ThreadNameResult> {
|
||||
let this = self.eval_context_mut();
|
||||
|
|
@ -99,9 +99,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
let mut name = this.read_c_str(name)?.to_owned();
|
||||
|
||||
// Comparing with `>=` to account for null terminator.
|
||||
if name.len() >= name_max_len {
|
||||
if name.len().to_u64() >= name_max_len {
|
||||
if truncate {
|
||||
name.truncate(name_max_len.saturating_sub(1));
|
||||
name.truncate(name_max_len.saturating_sub(1).try_into().unwrap());
|
||||
} else {
|
||||
return interp_ok(ThreadNameResult::NameTooLong);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -238,8 +238,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
// Of course we cannot use `windows_check_buffer_size` here since this uses
|
||||
// a different method for dealing with a too-small buffer than the other functions...
|
||||
let (success, len) = this.write_path_to_wide_str(home, buf, size_avail.into())?;
|
||||
// The Windows docs just say that this is written on failure. But std
|
||||
// seems to rely on it always being written.
|
||||
// The Windows docs just say that this is written on failure, but std relies on it
|
||||
// always being written. Also see <https://github.com/rust-lang/rust/issues/141254>.
|
||||
this.write_scalar(Scalar::from_u32(len.try_into().unwrap()), &size)?;
|
||||
if success {
|
||||
Scalar::from_i32(1) // return TRUE
|
||||
|
|
|
|||
|
|
@ -195,69 +195,52 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
|
||||
// File related shims
|
||||
"NtWriteFile" => {
|
||||
if !this.frame_in_std() {
|
||||
throw_unsup_format!(
|
||||
"`NtWriteFile` support is crude and just enough for stdout to work"
|
||||
);
|
||||
}
|
||||
|
||||
let [
|
||||
handle,
|
||||
_event,
|
||||
_apc_routine,
|
||||
_apc_context,
|
||||
event,
|
||||
apc_routine,
|
||||
apc_context,
|
||||
io_status_block,
|
||||
buf,
|
||||
n,
|
||||
byte_offset,
|
||||
_key,
|
||||
key,
|
||||
] = this.check_shim(abi, sys_conv, link_name, args)?;
|
||||
let handle = this.read_target_isize(handle)?;
|
||||
let buf = this.read_pointer(buf)?;
|
||||
let n = this.read_scalar(n)?.to_u32()?;
|
||||
let byte_offset = this.read_target_usize(byte_offset)?; // is actually a pointer
|
||||
let io_status_block = this
|
||||
.deref_pointer_as(io_status_block, this.windows_ty_layout("IO_STATUS_BLOCK"))?;
|
||||
|
||||
if byte_offset != 0 {
|
||||
throw_unsup_format!(
|
||||
"`NtWriteFile` `ByteOffset` parameter is non-null, which is unsupported"
|
||||
);
|
||||
}
|
||||
|
||||
let written = if handle == -11 || handle == -12 {
|
||||
// stdout/stderr
|
||||
use io::Write;
|
||||
|
||||
let buf_cont =
|
||||
this.read_bytes_ptr_strip_provenance(buf, Size::from_bytes(u64::from(n)))?;
|
||||
let res = if this.machine.mute_stdout_stderr {
|
||||
Ok(buf_cont.len())
|
||||
} else if handle == -11 {
|
||||
io::stdout().write(buf_cont)
|
||||
} else {
|
||||
io::stderr().write(buf_cont)
|
||||
};
|
||||
// We write at most `n` bytes, which is a `u32`, so we cannot have written more than that.
|
||||
res.ok().map(|n| u32::try_from(n).unwrap())
|
||||
} else {
|
||||
throw_unsup_format!(
|
||||
"on Windows, writing to anything except stdout/stderr is not supported"
|
||||
)
|
||||
};
|
||||
// We have to put the result into io_status_block.
|
||||
if let Some(n) = written {
|
||||
let io_status_information =
|
||||
this.project_field_named(&io_status_block, "Information")?;
|
||||
this.write_scalar(
|
||||
Scalar::from_target_usize(n.into(), this),
|
||||
&io_status_information,
|
||||
)?;
|
||||
}
|
||||
// Return whether this was a success. >= 0 is success.
|
||||
// For the error code we arbitrarily pick 0xC0000185, STATUS_IO_DEVICE_ERROR.
|
||||
this.write_scalar(
|
||||
Scalar::from_u32(if written.is_some() { 0 } else { 0xC0000185u32 }),
|
||||
this.NtWriteFile(
|
||||
handle,
|
||||
event,
|
||||
apc_routine,
|
||||
apc_context,
|
||||
io_status_block,
|
||||
buf,
|
||||
n,
|
||||
byte_offset,
|
||||
key,
|
||||
dest,
|
||||
)?;
|
||||
}
|
||||
"NtReadFile" => {
|
||||
let [
|
||||
handle,
|
||||
event,
|
||||
apc_routine,
|
||||
apc_context,
|
||||
io_status_block,
|
||||
buf,
|
||||
n,
|
||||
byte_offset,
|
||||
key,
|
||||
] = this.check_shim(abi, sys_conv, link_name, args)?;
|
||||
this.NtReadFile(
|
||||
handle,
|
||||
event,
|
||||
apc_routine,
|
||||
apc_context,
|
||||
io_status_block,
|
||||
buf,
|
||||
n,
|
||||
byte_offset,
|
||||
key,
|
||||
dest,
|
||||
)?;
|
||||
}
|
||||
|
|
@ -322,6 +305,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
let res = this.DeleteFileW(file_name)?;
|
||||
this.write_scalar(res, dest)?;
|
||||
}
|
||||
"SetFilePointerEx" => {
|
||||
let [file, distance_to_move, new_file_pointer, move_method] =
|
||||
this.check_shim(abi, sys_conv, link_name, args)?;
|
||||
let res =
|
||||
this.SetFilePointerEx(file, distance_to_move, new_file_pointer, move_method)?;
|
||||
this.write_scalar(res, dest)?;
|
||||
}
|
||||
|
||||
// Allocation
|
||||
"HeapAlloc" => {
|
||||
|
|
@ -700,12 +690,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
}
|
||||
"GetStdHandle" => {
|
||||
let [which] = this.check_shim(abi, sys_conv, link_name, args)?;
|
||||
let which = this.read_scalar(which)?.to_i32()?;
|
||||
// We just make this the identity function, so we know later in `NtWriteFile` which
|
||||
// one it is. This is very fake, but libtest needs it so we cannot make it a
|
||||
// std-only shim.
|
||||
// FIXME: this should return real HANDLEs when io support is added
|
||||
this.write_scalar(Scalar::from_target_isize(which.into(), this), dest)?;
|
||||
let res = this.GetStdHandle(which)?;
|
||||
this.write_scalar(res, dest)?;
|
||||
}
|
||||
"CloseHandle" => {
|
||||
let [handle] = this.check_shim(abi, sys_conv, link_name, args)?;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use std::fs::{Metadata, OpenOptions};
|
||||
use std::io;
|
||||
use std::io::SeekFrom;
|
||||
use std::path::PathBuf;
|
||||
use std::time::SystemTime;
|
||||
|
||||
|
|
@ -390,6 +391,267 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn NtWriteFile(
|
||||
&mut self,
|
||||
handle: &OpTy<'tcx>, // HANDLE
|
||||
event: &OpTy<'tcx>, // HANDLE
|
||||
apc_routine: &OpTy<'tcx>, // PIO_APC_ROUTINE
|
||||
apc_ctx: &OpTy<'tcx>, // PVOID
|
||||
io_status_block: &OpTy<'tcx>, // PIO_STATUS_BLOCK
|
||||
buf: &OpTy<'tcx>, // PVOID
|
||||
n: &OpTy<'tcx>, // ULONG
|
||||
byte_offset: &OpTy<'tcx>, // PLARGE_INTEGER
|
||||
key: &OpTy<'tcx>, // PULONG
|
||||
dest: &MPlaceTy<'tcx>, // return type: NTSTATUS
|
||||
) -> InterpResult<'tcx, ()> {
|
||||
let this = self.eval_context_mut();
|
||||
let handle = this.read_handle(handle, "NtWriteFile")?;
|
||||
let event = this.read_handle(event, "NtWriteFile")?;
|
||||
let apc_routine = this.read_pointer(apc_routine)?;
|
||||
let apc_ctx = this.read_pointer(apc_ctx)?;
|
||||
let buf = this.read_pointer(buf)?;
|
||||
let count = this.read_scalar(n)?.to_u32()?;
|
||||
let byte_offset = this.read_target_usize(byte_offset)?; // is actually a pointer, but we only support null
|
||||
let key = this.read_pointer(key)?;
|
||||
let io_status_block =
|
||||
this.deref_pointer_as(io_status_block, this.windows_ty_layout("IO_STATUS_BLOCK"))?;
|
||||
|
||||
if event != Handle::Null {
|
||||
throw_unsup_format!(
|
||||
"`NtWriteFile` `Event` parameter is non-null, which is unsupported"
|
||||
);
|
||||
}
|
||||
|
||||
if !this.ptr_is_null(apc_routine)? {
|
||||
throw_unsup_format!(
|
||||
"`NtWriteFile` `ApcRoutine` parameter is non-null, which is unsupported"
|
||||
);
|
||||
}
|
||||
|
||||
if !this.ptr_is_null(apc_ctx)? {
|
||||
throw_unsup_format!(
|
||||
"`NtWriteFile` `ApcContext` parameter is non-null, which is unsupported"
|
||||
);
|
||||
}
|
||||
|
||||
if byte_offset != 0 {
|
||||
throw_unsup_format!(
|
||||
"`NtWriteFile` `ByteOffset` parameter is non-null, which is unsupported"
|
||||
);
|
||||
}
|
||||
|
||||
if !this.ptr_is_null(key)? {
|
||||
throw_unsup_format!("`NtWriteFile` `Key` parameter is non-null, which is unsupported");
|
||||
}
|
||||
|
||||
let fd = match handle {
|
||||
Handle::File(fd) => fd,
|
||||
_ => this.invalid_handle("NtWriteFile")?,
|
||||
};
|
||||
|
||||
let Some(desc) = this.machine.fds.get(fd) else { this.invalid_handle("NtWriteFile")? };
|
||||
|
||||
// Windows writes the output code to IO_STATUS_BLOCK.Status, and number of bytes written
|
||||
// to IO_STATUS_BLOCK.Information.
|
||||
// The status block value and the returned value don't need to match - but
|
||||
// for the cases implemented by miri so far, we can choose to decide that they do.
|
||||
let io_status = {
|
||||
let anon = this.project_field_named(&io_status_block, "Anonymous")?;
|
||||
this.project_field_named(&anon, "Status")?
|
||||
};
|
||||
let io_status_info = this.project_field_named(&io_status_block, "Information")?;
|
||||
|
||||
let finish = {
|
||||
let io_status = io_status.clone();
|
||||
let io_status_info = io_status_info.clone();
|
||||
let dest = dest.clone();
|
||||
callback!(
|
||||
@capture<'tcx> {
|
||||
count: u32,
|
||||
io_status: MPlaceTy<'tcx>,
|
||||
io_status_info: MPlaceTy<'tcx>,
|
||||
dest: MPlaceTy<'tcx>,
|
||||
}
|
||||
|this, result: Result<usize, IoError>| {
|
||||
match result {
|
||||
Ok(read_size) => {
|
||||
assert!(read_size <= count.try_into().unwrap());
|
||||
// This must fit since `count` fits.
|
||||
this.write_int(u64::try_from(read_size).unwrap(), &io_status_info)?;
|
||||
this.write_int(0, &io_status)?;
|
||||
this.write_int(0, &dest)
|
||||
}
|
||||
Err(e) => {
|
||||
this.write_int(0, &io_status_info)?;
|
||||
let status = e.into_ntstatus();
|
||||
this.write_int(status, &io_status)?;
|
||||
this.write_int(status, &dest)
|
||||
}
|
||||
}}
|
||||
)
|
||||
};
|
||||
|
||||
desc.write(this.machine.communicate(), buf, count.try_into().unwrap(), this, finish)?;
|
||||
|
||||
// Return status is written to `dest` and `io_status_block` on callback completion.
|
||||
interp_ok(())
|
||||
}
|
||||
|
||||
fn NtReadFile(
|
||||
&mut self,
|
||||
handle: &OpTy<'tcx>, // HANDLE
|
||||
event: &OpTy<'tcx>, // HANDLE
|
||||
apc_routine: &OpTy<'tcx>, // PIO_APC_ROUTINE
|
||||
apc_ctx: &OpTy<'tcx>, // PVOID
|
||||
io_status_block: &OpTy<'tcx>, // PIO_STATUS_BLOCK
|
||||
buf: &OpTy<'tcx>, // PVOID
|
||||
n: &OpTy<'tcx>, // ULONG
|
||||
byte_offset: &OpTy<'tcx>, // PLARGE_INTEGER
|
||||
key: &OpTy<'tcx>, // PULONG
|
||||
dest: &MPlaceTy<'tcx>, // return type: NTSTATUS
|
||||
) -> InterpResult<'tcx, ()> {
|
||||
let this = self.eval_context_mut();
|
||||
let handle = this.read_handle(handle, "NtReadFile")?;
|
||||
let event = this.read_handle(event, "NtReadFile")?;
|
||||
let apc_routine = this.read_pointer(apc_routine)?;
|
||||
let apc_ctx = this.read_pointer(apc_ctx)?;
|
||||
let buf = this.read_pointer(buf)?;
|
||||
let count = this.read_scalar(n)?.to_u32()?;
|
||||
let byte_offset = this.read_target_usize(byte_offset)?; // is actually a pointer, but we only support null
|
||||
let key = this.read_pointer(key)?;
|
||||
let io_status_block =
|
||||
this.deref_pointer_as(io_status_block, this.windows_ty_layout("IO_STATUS_BLOCK"))?;
|
||||
|
||||
if event != Handle::Null {
|
||||
throw_unsup_format!("`NtReadFile` `Event` parameter is non-null, which is unsupported");
|
||||
}
|
||||
|
||||
if !this.ptr_is_null(apc_routine)? {
|
||||
throw_unsup_format!(
|
||||
"`NtReadFile` `ApcRoutine` parameter is non-null, which is unsupported"
|
||||
);
|
||||
}
|
||||
|
||||
if !this.ptr_is_null(apc_ctx)? {
|
||||
throw_unsup_format!(
|
||||
"`NtReadFile` `ApcContext` parameter is non-null, which is unsupported"
|
||||
);
|
||||
}
|
||||
|
||||
if byte_offset != 0 {
|
||||
throw_unsup_format!(
|
||||
"`NtReadFile` `ByteOffset` parameter is non-null, which is unsupported"
|
||||
);
|
||||
}
|
||||
|
||||
if !this.ptr_is_null(key)? {
|
||||
throw_unsup_format!("`NtReadFile` `Key` parameter is non-null, which is unsupported");
|
||||
}
|
||||
|
||||
// See NtWriteFile above for commentary on this
|
||||
let io_status = {
|
||||
let anon = this.project_field_named(&io_status_block, "Anonymous")?;
|
||||
this.project_field_named(&anon, "Status")?
|
||||
};
|
||||
let io_status_info = this.project_field_named(&io_status_block, "Information")?;
|
||||
|
||||
let finish = {
|
||||
let io_status = io_status.clone();
|
||||
let io_status_info = io_status_info.clone();
|
||||
let dest = dest.clone();
|
||||
callback!(
|
||||
@capture<'tcx> {
|
||||
count: u32,
|
||||
io_status: MPlaceTy<'tcx>,
|
||||
io_status_info: MPlaceTy<'tcx>,
|
||||
dest: MPlaceTy<'tcx>,
|
||||
}
|
||||
|this, result: Result<usize, IoError>| {
|
||||
match result {
|
||||
Ok(read_size) => {
|
||||
assert!(read_size <= count.try_into().unwrap());
|
||||
// This must fit since `count` fits.
|
||||
this.write_int(u64::try_from(read_size).unwrap(), &io_status_info)?;
|
||||
this.write_int(0, &io_status)?;
|
||||
this.write_int(0, &dest)
|
||||
}
|
||||
Err(e) => {
|
||||
this.write_int(0, &io_status_info)?;
|
||||
let status = e.into_ntstatus();
|
||||
this.write_int(status, &io_status)?;
|
||||
this.write_int(status, &dest)
|
||||
}
|
||||
}}
|
||||
)
|
||||
};
|
||||
|
||||
let fd = match handle {
|
||||
Handle::File(fd) => fd,
|
||||
_ => this.invalid_handle("NtWriteFile")?,
|
||||
};
|
||||
|
||||
let Some(desc) = this.machine.fds.get(fd) else { this.invalid_handle("NtReadFile")? };
|
||||
|
||||
desc.read(this.machine.communicate(), buf, count.try_into().unwrap(), this, finish)?;
|
||||
|
||||
// See NtWriteFile for commentary on this
|
||||
interp_ok(())
|
||||
}
|
||||
|
||||
fn SetFilePointerEx(
|
||||
&mut self,
|
||||
file: &OpTy<'tcx>, // HANDLE
|
||||
dist_to_move: &OpTy<'tcx>, // LARGE_INTEGER
|
||||
new_fp: &OpTy<'tcx>, // PLARGE_INTEGER
|
||||
move_method: &OpTy<'tcx>, // DWORD
|
||||
) -> InterpResult<'tcx, Scalar> {
|
||||
// ^ Returns BOOL (i32 on Windows)
|
||||
let this = self.eval_context_mut();
|
||||
let file = this.read_handle(file, "SetFilePointerEx")?;
|
||||
let dist_to_move = this.read_scalar(dist_to_move)?.to_i64()?;
|
||||
let new_fp_ptr = this.read_pointer(new_fp)?;
|
||||
let move_method = this.read_scalar(move_method)?.to_u32()?;
|
||||
|
||||
let fd = match file {
|
||||
Handle::File(fd) => fd,
|
||||
_ => this.invalid_handle("SetFilePointerEx")?,
|
||||
};
|
||||
|
||||
let Some(desc) = this.machine.fds.get(fd) else {
|
||||
throw_unsup_format!("`SetFilePointerEx` is only supported on file backed handles");
|
||||
};
|
||||
|
||||
let file_begin = this.eval_windows_u32("c", "FILE_BEGIN");
|
||||
let file_current = this.eval_windows_u32("c", "FILE_CURRENT");
|
||||
let file_end = this.eval_windows_u32("c", "FILE_END");
|
||||
|
||||
let seek = if move_method == file_begin {
|
||||
SeekFrom::Start(dist_to_move.try_into().unwrap())
|
||||
} else if move_method == file_current {
|
||||
SeekFrom::Current(dist_to_move)
|
||||
} else if move_method == file_end {
|
||||
SeekFrom::End(dist_to_move)
|
||||
} else {
|
||||
throw_unsup_format!("Invalid move method: {move_method}")
|
||||
};
|
||||
|
||||
match desc.seek(this.machine.communicate(), seek)? {
|
||||
Ok(n) => {
|
||||
if !this.ptr_is_null(new_fp_ptr)? {
|
||||
this.write_scalar(
|
||||
Scalar::from_i64(n.try_into().unwrap()),
|
||||
&this.deref_pointer_as(new_fp, this.machine.layouts.i64)?,
|
||||
)?;
|
||||
}
|
||||
interp_ok(this.eval_windows("c", "TRUE"))
|
||||
}
|
||||
Err(e) => {
|
||||
this.set_last_error(e)?;
|
||||
interp_ok(this.eval_windows("c", "FALSE"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Windows FILETIME is measured in 100-nanosecs since 1601
|
||||
|
|
@ -401,7 +663,7 @@ fn extract_windows_epoch<'tcx>(
|
|||
Some(time) => {
|
||||
let duration = ecx.system_time_since_windows_epoch(&time)?;
|
||||
let duration_ticks = ecx.windows_ticks_for(duration)?;
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
#[expect(clippy::as_conversions)]
|
||||
interp_ok(Some((duration_ticks as u32, (duration_ticks >> 32) as u32)))
|
||||
}
|
||||
None => interp_ok(None),
|
||||
|
|
|
|||
|
|
@ -70,8 +70,7 @@ impl Handle {
|
|||
Self::Null => 0,
|
||||
Self::Pseudo(pseudo_handle) => pseudo_handle.value(),
|
||||
Self::Thread(thread) => thread.to_u32(),
|
||||
#[expect(clippy::cast_sign_loss)]
|
||||
Self::File(fd) => fd as u32,
|
||||
Self::File(fd) => fd.cast_unsigned(),
|
||||
// INVALID_HANDLE_VALUE is -1. This fact is explicitly declared or implied in several
|
||||
// pages of Windows documentation.
|
||||
// 1: https://learn.microsoft.com/en-us/dotnet/api/microsoft.win32.safehandles.safefilehandle?view=net-9.0
|
||||
|
|
@ -124,11 +123,10 @@ impl Handle {
|
|||
Self::NULL_DISCRIMINANT if data == 0 => Some(Self::Null),
|
||||
Self::PSEUDO_DISCRIMINANT => Some(Self::Pseudo(PseudoHandle::from_value(data)?)),
|
||||
Self::THREAD_DISCRIMINANT => Some(Self::Thread(ThreadId::new_unchecked(data))),
|
||||
#[expect(clippy::cast_possible_wrap)]
|
||||
Self::FILE_DISCRIMINANT => {
|
||||
// This cast preserves all bits.
|
||||
assert_eq!(size_of_val(&data), size_of::<FdNum>());
|
||||
Some(Self::File(data as FdNum))
|
||||
Some(Self::File(data.cast_signed()))
|
||||
}
|
||||
Self::INVALID_DISCRIMINANT => Some(Self::Invalid),
|
||||
_ => None,
|
||||
|
|
@ -156,8 +154,7 @@ impl Handle {
|
|||
pub fn to_scalar(self, cx: &impl HasDataLayout) -> Scalar {
|
||||
// 64-bit handles are sign extended 32-bit handles
|
||||
// see https://docs.microsoft.com/en-us/windows/win32/winprog64/interprocess-communication
|
||||
#[expect(clippy::cast_possible_wrap)] // we want it to wrap
|
||||
let signed_handle = self.to_packed() as i32;
|
||||
let signed_handle = self.to_packed().cast_signed();
|
||||
Scalar::from_target_isize(signed_handle.into(), cx)
|
||||
}
|
||||
|
||||
|
|
@ -171,9 +168,8 @@ impl Handle {
|
|||
) -> InterpResult<'tcx, Result<Self, HandleError>> {
|
||||
let sign_extended_handle = handle.to_target_isize(cx)?;
|
||||
|
||||
#[expect(clippy::cast_sign_loss)] // we want to lose the sign
|
||||
let handle = if let Ok(signed_handle) = i32::try_from(sign_extended_handle) {
|
||||
signed_handle as u32
|
||||
signed_handle.cast_unsigned()
|
||||
} else {
|
||||
// if a handle doesn't fit in an i32, it isn't valid.
|
||||
return interp_ok(Err(HandleError::InvalidHandle));
|
||||
|
|
@ -224,6 +220,30 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
)))
|
||||
}
|
||||
|
||||
fn GetStdHandle(&mut self, which: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
|
||||
let this = self.eval_context_mut();
|
||||
let which = this.read_scalar(which)?.to_i32()?;
|
||||
|
||||
let stdin = this.eval_windows("c", "STD_INPUT_HANDLE").to_i32()?;
|
||||
let stdout = this.eval_windows("c", "STD_OUTPUT_HANDLE").to_i32()?;
|
||||
let stderr = this.eval_windows("c", "STD_ERROR_HANDLE").to_i32()?;
|
||||
|
||||
// These values don't mean anything on Windows, but Miri unconditionally sets them up to the
|
||||
// unix in/out/err descriptors. So we take advantage of that.
|
||||
// Due to the `Handle` encoding, these values will not be directly exposed to the user.
|
||||
let fd_num = if which == stdin {
|
||||
0
|
||||
} else if which == stdout {
|
||||
1
|
||||
} else if which == stderr {
|
||||
2
|
||||
} else {
|
||||
throw_unsup_format!("Invalid argument to `GetStdHandle`: {which}")
|
||||
};
|
||||
let handle = Handle::File(fd_num);
|
||||
interp_ok(handle.to_scalar(this))
|
||||
}
|
||||
|
||||
fn CloseHandle(&mut self, handle_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
|
||||
let this = self.eval_context_mut();
|
||||
|
||||
|
|
|
|||
|
|
@ -133,12 +133,12 @@ fn affine_transform<'tcx>(
|
|||
// This is a evaluated at compile time. Trait based conversion is not available.
|
||||
/// See <https://www.corsix.org/content/galois-field-instructions-2021-cpus> for the
|
||||
/// definition of `gf_inv` which was used for the creation of this table.
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
static TABLE: [u8; 256] = {
|
||||
let mut array = [0; 256];
|
||||
|
||||
let mut i = 1;
|
||||
while i < 256 {
|
||||
#[expect(clippy::as_conversions)] // no `try_from` in const...
|
||||
let mut x = i as u8;
|
||||
let mut y = gf2p8_mul(x, x);
|
||||
x = y;
|
||||
|
|
@ -160,7 +160,7 @@ static TABLE: [u8; 256] = {
|
|||
/// polynomial representation with the reduction polynomial x^8 + x^4 + x^3 + x + 1.
|
||||
/// See <https://www.corsix.org/content/galois-field-instructions-2021-cpus> for details.
|
||||
// This is a const function. Trait based conversion is not available.
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
#[expect(clippy::as_conversions)]
|
||||
const fn gf2p8_mul(left: u8, right: u8) -> u8 {
|
||||
// This implementation is based on the `gf2p8mul_byte` definition found inside the Intel intrinsics guide.
|
||||
// See https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=gf2p8mul
|
||||
|
|
|
|||
|
|
@ -1110,7 +1110,7 @@ fn pmulhrsw<'tcx>(
|
|||
|
||||
// The result of this operation can overflow a signed 16-bit integer.
|
||||
// When `left` and `right` are -0x8000, the result is 0x8000.
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
#[expect(clippy::as_conversions)]
|
||||
let res = res as i16;
|
||||
|
||||
ecx.write_scalar(Scalar::from_i16(res), &dest)?;
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
// We reverse the order because x86 is little endian but the copied implementation uses
|
||||
// big endian.
|
||||
for (i, part) in val.into_iter().rev().enumerate() {
|
||||
let projected = &ecx.project_index(dest, i.try_into().unwrap())?;
|
||||
let projected = &ecx.project_index(dest, i.to_u64())?;
|
||||
ecx.write_scalar(Scalar::from_u32(part), projected)?;
|
||||
}
|
||||
interp_ok(())
|
||||
|
|
|
|||
|
|
@ -440,7 +440,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
let crc = if bit_size == 64 {
|
||||
// The 64-bit version will only consider the lower 32 bits,
|
||||
// while the upper 32 bits get discarded.
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
#[expect(clippy::as_conversions)]
|
||||
u128::from((left.to_u64()? as u32).reverse_bits())
|
||||
} else {
|
||||
u128::from(left.to_u32()?.reverse_bits())
|
||||
|
|
|
|||
|
|
@ -25,6 +25,13 @@ page_size = "0.6"
|
|||
tokio = { version = "1", features = ["macros", "rt-multi-thread", "time", "net", "fs", "sync", "signal", "io-util"] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows-sys = { version = "0.59", features = ["Win32_Foundation", "Win32_System_Threading", "Win32_Storage_FileSystem", "Win32_Security"] }
|
||||
windows-sys = { version = "0.59", features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_System_Threading",
|
||||
"Win32_Storage_FileSystem",
|
||||
"Win32_Security",
|
||||
"Win32_System_IO",
|
||||
"Wdk_Storage_FileSystem",
|
||||
] }
|
||||
|
||||
[workspace]
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
//@ignore-target: windows # No libc IO on Windows
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
let mut bytes = [0u8; 512];
|
||||
unsafe {
|
||||
libc::read(0, bytes.as_mut_ptr() as *mut libc::c_void, 512); //~ ERROR: `read` from stdin not available when isolation is enabled
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
//@compile-flags: -Zmiri-disable-stacked-borrows
|
||||
// Needs atomic accesses larger than the pointer size
|
||||
//@ignore-bitwidth: 64
|
||||
//@ignore-target: mips-
|
||||
|
||||
use std::sync::atomic::{AtomicI64, Ordering};
|
||||
|
||||
|
|
|
|||
12
src/tools/miri/tests/fail/shims/isolated_stdin.rs
Normal file
12
src/tools/miri/tests/fail/shims/isolated_stdin.rs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
//@error-in-other-file: `read` from stdin not available when isolation is enabled
|
||||
//@normalize-stderr-test: "src/sys/.*\.rs" -> "$$FILE"
|
||||
//@normalize-stderr-test: "\nLL \| .*" -> ""
|
||||
//@normalize-stderr-test: "\n... .*" -> ""
|
||||
//@normalize-stderr-test: "\| +[|_^]+" -> "| ^"
|
||||
//@normalize-stderr-test: "\n *= note:.*" -> ""
|
||||
use std::io::{self, Read};
|
||||
|
||||
fn main() {
|
||||
let mut bytes = [0u8; 512];
|
||||
io::stdin().read(&mut bytes).unwrap();
|
||||
}
|
||||
|
|
@ -1,13 +1,14 @@
|
|||
error: unsupported operation: `read` from stdin not available when isolation is enabled
|
||||
--> tests/fail-dep/libc/fs/isolated_stdin.rs:LL:CC
|
||||
--> RUSTLIB/std/$FILE:LL:CC
|
||||
|
|
||||
LL | libc::read(0, bytes.as_mut_ptr() as *mut libc::c_void, 512);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `read` from stdin not available when isolation is enabled
|
||||
| ^ `read` from stdin not available when isolation is enabled
|
||||
|
|
||||
= help: set `MIRIFLAGS=-Zmiri-disable-isolation` to disable isolation;
|
||||
= help: or set `MIRIFLAGS=-Zmiri-isolation-error=warn` to make Miri return an error code from isolated operations (if supported for that operation) and continue with a warning
|
||||
= note: BACKTRACE:
|
||||
= note: inside `main` at tests/fail-dep/libc/fs/isolated_stdin.rs:LL:CC
|
||||
note: inside `main`
|
||||
--> tests/fail/shims/isolated_stdin.rs:LL:CC
|
||||
|
|
||||
| ^
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
|
|
@ -5,6 +5,8 @@ fn main() {
|
|||
let bad = unsafe { std::mem::transmute::<u128, &[u8]>(42 << 64) };
|
||||
#[cfg(all(target_endian = "little", target_pointer_width = "32"))]
|
||||
let bad = unsafe { std::mem::transmute::<u64, &[u8]>(42) };
|
||||
#[cfg(all(target_endian = "big", target_pointer_width = "32"))]
|
||||
let bad = unsafe { std::mem::transmute::<u64, &[u8]>(42 << 32) };
|
||||
// This created a slice with length 0, so the following will fail the bounds check.
|
||||
bad[0];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,10 @@ fn main() {
|
|||
assert!(libc::fcntl(1, libc::F_DUPFD, 0) >= 0);
|
||||
}
|
||||
|
||||
// Although `readlink` and `stat` require disable-isolation mode
|
||||
// to properly run, they are tested with isolation mode on to check the error emitted
|
||||
// with `-Zmiri-isolation-error=warn-nobacktrace`.
|
||||
|
||||
// test `readlink`
|
||||
let mut buf = vec![0; "foo_link.txt".len() + 1];
|
||||
unsafe {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
//@only-target: freebsd
|
||||
//@compile-flags: -Zmiri-num-cpus=256
|
||||
|
||||
use std::mem;
|
||||
|
||||
fn getaffinity() {
|
||||
let mut set: libc::cpuset_t = unsafe { mem::zeroed() };
|
||||
unsafe {
|
||||
if libc::cpuset_getaffinity(
|
||||
libc::CPU_LEVEL_WHICH,
|
||||
libc::CPU_WHICH_PID,
|
||||
-1,
|
||||
size_of::<libc::cpuset_t>(),
|
||||
&mut set,
|
||||
) == 0
|
||||
{
|
||||
assert!(libc::CPU_COUNT(&set) == 256);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_small_cpu_mask() {
|
||||
let mut set: libc::cpuset_t = unsafe { core::mem::MaybeUninit::zeroed().assume_init() };
|
||||
|
||||
// 256 CPUs so we need 32 bytes to represent this mask.
|
||||
// According to Freebsd only when `cpusetsize` is smaller than this value, does it return with ERANGE
|
||||
|
||||
let err = unsafe {
|
||||
libc::cpuset_getaffinity(libc::CPU_LEVEL_WHICH, libc::CPU_WHICH_PID, -1, 32, &mut set)
|
||||
};
|
||||
assert_eq!(err, 0, "Success Expected");
|
||||
|
||||
// 31 is not enough, so it should fail.
|
||||
let err = unsafe {
|
||||
libc::cpuset_getaffinity(libc::CPU_LEVEL_WHICH, libc::CPU_WHICH_PID, -1, 31, &mut set)
|
||||
};
|
||||
assert_eq!(err, -1, "Expected Failure");
|
||||
assert_eq!(std::io::Error::last_os_error().raw_os_error().unwrap(), libc::ERANGE);
|
||||
|
||||
// Zero should fail as well.
|
||||
let err = unsafe {
|
||||
libc::cpuset_getaffinity(libc::CPU_LEVEL_WHICH, libc::CPU_WHICH_PID, -1, 0, &mut set)
|
||||
};
|
||||
assert_eq!(err, -1, "Expected Failure");
|
||||
assert_eq!(std::io::Error::last_os_error().raw_os_error().unwrap(), libc::ERANGE);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
getaffinity();
|
||||
get_small_cpu_mask();
|
||||
}
|
||||
|
|
@ -2,25 +2,28 @@
|
|||
//@compile-flags: -Zmiri-disable-isolation
|
||||
#![allow(nonstandard_style)]
|
||||
|
||||
use std::io::ErrorKind;
|
||||
use std::io::{ErrorKind, Read, Write};
|
||||
use std::os::windows::ffi::OsStrExt;
|
||||
use std::os::windows::io::AsRawHandle;
|
||||
use std::path::Path;
|
||||
use std::ptr;
|
||||
use std::{fs, ptr};
|
||||
|
||||
#[path = "../../utils/mod.rs"]
|
||||
mod utils;
|
||||
|
||||
use windows_sys::Wdk::Storage::FileSystem::{NtReadFile, NtWriteFile};
|
||||
use windows_sys::Win32::Foundation::{
|
||||
CloseHandle, ERROR_ACCESS_DENIED, ERROR_ALREADY_EXISTS, ERROR_IO_DEVICE, GENERIC_READ,
|
||||
GENERIC_WRITE, GetLastError, RtlNtStatusToDosError, STATUS_ACCESS_DENIED,
|
||||
STATUS_IO_DEVICE_ERROR,
|
||||
STATUS_IO_DEVICE_ERROR, STATUS_SUCCESS, SetLastError,
|
||||
};
|
||||
use windows_sys::Win32::Storage::FileSystem::{
|
||||
BY_HANDLE_FILE_INFORMATION, CREATE_ALWAYS, CREATE_NEW, CreateFileW, DeleteFileW,
|
||||
FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_NORMAL, FILE_FLAG_BACKUP_SEMANTICS,
|
||||
FILE_FLAG_OPEN_REPARSE_POINT, FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE,
|
||||
GetFileInformationByHandle, OPEN_ALWAYS, OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_NORMAL, FILE_BEGIN, FILE_CURRENT,
|
||||
FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, FILE_SHARE_DELETE, FILE_SHARE_READ,
|
||||
FILE_SHARE_WRITE, GetFileInformationByHandle, OPEN_ALWAYS, OPEN_EXISTING, SetFilePointerEx,
|
||||
};
|
||||
use windows_sys::Win32::System::IO::IO_STATUS_BLOCK;
|
||||
|
||||
fn main() {
|
||||
unsafe {
|
||||
|
|
@ -31,6 +34,8 @@ fn main() {
|
|||
test_open_dir_reparse();
|
||||
test_delete_file();
|
||||
test_ntstatus_to_dos();
|
||||
test_file_read_write();
|
||||
test_file_seek();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -199,13 +204,13 @@ unsafe fn test_open_dir_reparse() {
|
|||
unsafe fn test_delete_file() {
|
||||
let temp = utils::tmp().join("test_delete_file.txt");
|
||||
let raw_path = to_wide_cstr(&temp);
|
||||
let _ = std::fs::File::create(&temp).unwrap();
|
||||
let _ = fs::File::create(&temp).unwrap();
|
||||
|
||||
if DeleteFileW(raw_path.as_ptr()) == 0 {
|
||||
panic!("Failed to delete file");
|
||||
}
|
||||
|
||||
match std::fs::File::open(temp) {
|
||||
match fs::File::open(temp) {
|
||||
Ok(_) => panic!("File not deleted"),
|
||||
Err(e) => assert!(e.kind() == ErrorKind::NotFound, "File not deleted"),
|
||||
}
|
||||
|
|
@ -217,6 +222,82 @@ unsafe fn test_ntstatus_to_dos() {
|
|||
assert_eq!(RtlNtStatusToDosError(STATUS_ACCESS_DENIED), ERROR_ACCESS_DENIED);
|
||||
}
|
||||
|
||||
unsafe fn test_file_read_write() {
|
||||
let temp = utils::tmp().join("test_file_read_write.txt");
|
||||
let file = fs::File::create(&temp).unwrap();
|
||||
let handle = file.as_raw_handle();
|
||||
|
||||
// Testing NtWriteFile doesn't clobber the error
|
||||
SetLastError(1234);
|
||||
|
||||
let text = b"Example text!";
|
||||
let mut status = std::mem::zeroed::<IO_STATUS_BLOCK>();
|
||||
let out = NtWriteFile(
|
||||
handle,
|
||||
ptr::null_mut(),
|
||||
None,
|
||||
ptr::null_mut(),
|
||||
&mut status,
|
||||
text.as_ptr().cast(),
|
||||
text.len() as u32,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
);
|
||||
|
||||
assert_eq!(out, status.Anonymous.Status);
|
||||
assert_eq!(out, STATUS_SUCCESS);
|
||||
assert_eq!(GetLastError(), 1234);
|
||||
|
||||
let file = fs::File::open(&temp).unwrap();
|
||||
let handle = file.as_raw_handle();
|
||||
|
||||
// Testing NtReadFile doesn't clobber the error
|
||||
SetLastError(1234);
|
||||
|
||||
let mut buffer = vec![0; 13];
|
||||
let out = NtReadFile(
|
||||
handle,
|
||||
ptr::null_mut(),
|
||||
None,
|
||||
ptr::null_mut(),
|
||||
&mut status,
|
||||
buffer.as_mut_ptr().cast(),
|
||||
buffer.len() as u32,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
);
|
||||
|
||||
assert_eq!(out, status.Anonymous.Status);
|
||||
assert_eq!(out, STATUS_SUCCESS);
|
||||
assert_eq!(buffer, text);
|
||||
assert_eq!(GetLastError(), 1234);
|
||||
}
|
||||
|
||||
unsafe fn test_file_seek() {
|
||||
let temp = utils::tmp().join("test_file_seek.txt");
|
||||
let mut file = fs::File::options().create(true).write(true).read(true).open(&temp).unwrap();
|
||||
file.write_all(b"Hello, World!\n").unwrap();
|
||||
|
||||
let handle = file.as_raw_handle();
|
||||
|
||||
if SetFilePointerEx(handle, 7, ptr::null_mut(), FILE_BEGIN) == 0 {
|
||||
panic!("Failed to seek");
|
||||
}
|
||||
|
||||
let mut buf = vec![0; 5];
|
||||
file.read(&mut buf).unwrap();
|
||||
assert_eq!(buf, b"World");
|
||||
|
||||
let mut pos = 0;
|
||||
if SetFilePointerEx(handle, -7, &mut pos, FILE_CURRENT) == 0 {
|
||||
panic!("Failed to seek");
|
||||
}
|
||||
buf.truncate(2);
|
||||
file.read_exact(&mut buf).unwrap();
|
||||
assert_eq!(buf, b", ");
|
||||
assert_eq!(pos, 5);
|
||||
}
|
||||
|
||||
fn to_wide_cstr(path: &Path) -> Vec<u16> {
|
||||
let mut raw_path = path.as_os_str().encode_wide().collect::<Vec<_>>();
|
||||
raw_path.extend([0, 0]);
|
||||
|
|
|
|||
|
|
@ -7,15 +7,17 @@
|
|||
#![allow(static_mut_refs)]
|
||||
|
||||
use std::sync::atomic::Ordering::*;
|
||||
use std::sync::atomic::{AtomicBool, AtomicIsize, AtomicPtr, AtomicU64, compiler_fence, fence};
|
||||
use std::sync::atomic::{AtomicBool, AtomicIsize, AtomicPtr, AtomicUsize, compiler_fence, fence};
|
||||
|
||||
fn main() {
|
||||
atomic_bool();
|
||||
atomic_all_ops();
|
||||
atomic_u64();
|
||||
atomic_fences();
|
||||
atomic_ptr();
|
||||
weak_sometimes_fails();
|
||||
|
||||
#[cfg(target_has_atomic = "64")]
|
||||
atomic_u64();
|
||||
}
|
||||
|
||||
fn atomic_bool() {
|
||||
|
|
@ -36,25 +38,10 @@ fn atomic_bool() {
|
|||
}
|
||||
}
|
||||
|
||||
// There isn't a trait to use to make this generic, so just use a macro
|
||||
macro_rules! compare_exchange_weak_loop {
|
||||
($atom:expr, $from:expr, $to:expr, $succ_order:expr, $fail_order:expr) => {
|
||||
loop {
|
||||
match $atom.compare_exchange_weak($from, $to, $succ_order, $fail_order) {
|
||||
Ok(n) => {
|
||||
assert_eq!(n, $from);
|
||||
break;
|
||||
}
|
||||
Err(n) => assert_eq!(n, $from),
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Make sure we can handle all the intrinsics
|
||||
fn atomic_all_ops() {
|
||||
static ATOMIC: AtomicIsize = AtomicIsize::new(0);
|
||||
static ATOMIC_UNSIGNED: AtomicU64 = AtomicU64::new(0);
|
||||
static ATOMIC_UNSIGNED: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
let load_orders = [Relaxed, Acquire, SeqCst];
|
||||
let stored_orders = [Relaxed, Release, SeqCst];
|
||||
|
|
@ -94,9 +81,26 @@ fn atomic_all_ops() {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(target_has_atomic = "64")]
|
||||
fn atomic_u64() {
|
||||
use std::sync::atomic::AtomicU64;
|
||||
static ATOMIC: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
// There isn't a trait to use to make this generic, so just use a macro
|
||||
macro_rules! compare_exchange_weak_loop {
|
||||
($atom:expr, $from:expr, $to:expr, $succ_order:expr, $fail_order:expr) => {
|
||||
loop {
|
||||
match $atom.compare_exchange_weak($from, $to, $succ_order, $fail_order) {
|
||||
Ok(n) => {
|
||||
assert_eq!(n, $from);
|
||||
break;
|
||||
}
|
||||
Err(n) => assert_eq!(n, $from),
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ATOMIC.store(1, SeqCst);
|
||||
assert_eq!(ATOMIC.compare_exchange(0, 0x100, AcqRel, Acquire), Err(1));
|
||||
assert_eq!(ATOMIC.compare_exchange(0, 1, Release, Relaxed), Err(1));
|
||||
|
|
|
|||
|
|
@ -17,20 +17,20 @@ mod utils;
|
|||
|
||||
fn main() {
|
||||
test_path_conversion();
|
||||
test_file();
|
||||
test_file_create_new();
|
||||
test_metadata();
|
||||
test_seek();
|
||||
test_errors();
|
||||
test_from_raw_os_error();
|
||||
// Windows file handling is very incomplete.
|
||||
if cfg!(not(windows)) {
|
||||
test_file();
|
||||
test_seek();
|
||||
test_file_clone();
|
||||
test_metadata();
|
||||
test_file_set_len();
|
||||
test_file_sync();
|
||||
test_errors();
|
||||
test_rename();
|
||||
test_directory();
|
||||
test_canonicalize();
|
||||
test_from_raw_os_error();
|
||||
#[cfg(unix)]
|
||||
test_pread_pwrite();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
use std::io::{self, IsTerminal};
|
||||
use std::io::{self, IsTerminal, Write};
|
||||
|
||||
fn main() {
|
||||
// We can't really assume that this is truly a terminal, and anyway on Windows Miri will always
|
||||
// return `false` here, but we can check that the call succeeds.
|
||||
io::stdout().write_all(b"stdout\n").unwrap();
|
||||
io::stderr().write_all(b"stderr\n").unwrap();
|
||||
|
||||
// We can't assume that this is truly a terminal, but we can check that the call succeeds.
|
||||
io::stdout().is_terminal();
|
||||
|
||||
// Ensure we can format `io::Error` created from OS errors
|
||||
|
|
|
|||
1
src/tools/miri/tests/pass/shims/io.stderr
Normal file
1
src/tools/miri/tests/pass/shims/io.stderr
Normal file
|
|
@ -0,0 +1 @@
|
|||
stderr
|
||||
1
src/tools/miri/tests/pass/shims/io.stdout
Normal file
1
src/tools/miri/tests/pass/shims/io.stdout
Normal file
|
|
@ -0,0 +1 @@
|
|||
stdout
|
||||
Loading…
Add table
Add a link
Reference in a new issue