blob: 8bffa9991c75af7bbb07a8e9bbaf18efecad9d05 [file] [log] [blame]
use rustc_target::abi::HasDataLayout;
use std::mem::variant_count;
use crate::*;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum PseudoHandle {
CurrentThread,
}
/// Miri representation of a Windows `HANDLE`
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Handle {
Null,
Pseudo(PseudoHandle),
Thread(ThreadId),
}
impl PseudoHandle {
const CURRENT_THREAD_VALUE: u32 = 0;
fn value(self) -> u32 {
match self {
Self::CurrentThread => Self::CURRENT_THREAD_VALUE,
}
}
fn from_value(value: u32) -> Option<Self> {
match value {
Self::CURRENT_THREAD_VALUE => Some(Self::CurrentThread),
_ => None,
}
}
}
impl Handle {
const NULL_DISCRIMINANT: u32 = 0;
const PSEUDO_DISCRIMINANT: u32 = 1;
const THREAD_DISCRIMINANT: u32 = 2;
fn discriminant(self) -> u32 {
match self {
Self::Null => Self::NULL_DISCRIMINANT,
Self::Pseudo(_) => Self::PSEUDO_DISCRIMINANT,
Self::Thread(_) => Self::THREAD_DISCRIMINANT,
}
}
fn data(self) -> u32 {
match self {
Self::Null => 0,
Self::Pseudo(pseudo_handle) => pseudo_handle.value(),
Self::Thread(thread) => thread.to_u32(),
}
}
fn packed_disc_size() -> u32 {
// ceil(log2(x)) is how many bits it takes to store x numbers
let variant_count = variant_count::<Self>();
// however, std's ilog2 is floor(log2(x))
let floor_log2 = variant_count.ilog2();
// we need to add one for non powers of two to compensate for the difference
#[allow(clippy::integer_arithmetic)] // cannot overflow
if variant_count.is_power_of_two() { floor_log2 } else { floor_log2 + 1 }
}
/// Converts a handle into its machine representation.
///
/// The upper [`Self::packed_disc_size()`] bits are used to store a discriminant corresponding to the handle variant.
/// The remaining bits are used for the variant's field.
///
/// None of this layout is guaranteed to applications by Windows or Miri.
fn to_packed(self) -> u32 {
let disc_size = Self::packed_disc_size();
let data_size = u32::BITS.checked_sub(disc_size).unwrap();
let discriminant = self.discriminant();
let data = self.data();
// make sure the discriminant fits into `disc_size` bits
assert!(discriminant < 2u32.pow(disc_size));
// make sure the data fits into `data_size` bits
assert!(data < 2u32.pow(data_size));
// packs the data into the lower `data_size` bits
// and packs the discriminant right above the data
#[allow(clippy::integer_arithmetic)] // cannot overflow
return discriminant << data_size | data;
}
fn new(discriminant: u32, data: u32) -> Option<Self> {
match discriminant {
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(data.into())),
_ => None,
}
}
/// see docs for `to_packed`
fn from_packed(handle: u32) -> Option<Self> {
let disc_size = Self::packed_disc_size();
let data_size = u32::BITS.checked_sub(disc_size).unwrap();
// the lower `data_size` bits of this mask are 1
#[allow(clippy::integer_arithmetic)] // cannot overflow
let data_mask = 2u32.pow(data_size) - 1;
// the discriminant is stored right above the lower `data_size` bits
#[allow(clippy::integer_arithmetic)] // cannot overflow
let discriminant = handle >> data_size;
// the data is stored in the lower `data_size` bits
let data = handle & data_mask;
Self::new(discriminant, data)
}
pub fn to_scalar(self, cx: &impl HasDataLayout) -> Scalar<Provenance> {
// 64-bit handles are sign extended 32-bit handles
// see https://docs.microsoft.com/en-us/windows/win32/winprog64/interprocess-communication
#[allow(clippy::cast_possible_wrap)] // we want it to wrap
let signed_handle = self.to_packed() as i32;
Scalar::from_target_isize(signed_handle.into(), cx)
}
pub fn from_scalar<'tcx>(
handle: Scalar<Provenance>,
cx: &impl HasDataLayout,
) -> InterpResult<'tcx, Option<Self>> {
let sign_extended_handle = handle.to_target_isize(cx)?;
#[allow(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
} else {
// if a handle doesn't fit in an i32, it isn't valid.
return Ok(None);
};
Ok(Self::from_packed(handle))
}
}
impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
#[allow(non_snake_case)]
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
fn invalid_handle(&mut self, function_name: &str) -> InterpResult<'tcx, !> {
throw_machine_stop!(TerminationInfo::Abort(format!(
"invalid handle passed to `{function_name}`"
)))
}
fn CloseHandle(&mut self, handle_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let handle = this.read_scalar(handle_op)?;
match Handle::from_scalar(handle, this)? {
Some(Handle::Thread(thread)) =>
this.detach_thread(thread, /*allow_terminated_joined*/ true)?,
_ => this.invalid_handle("CloseHandle")?,
}
Ok(())
}
}