| use std::collections::BTreeSet; |
| use std::num::NonZero; |
| use std::sync::Mutex; |
| use std::time::Duration; |
| use std::{cmp, iter}; |
| |
| use rand::RngCore; |
| use rustc_abi::{Align, ExternAbi, FieldIdx, FieldsShape, Size, Variants}; |
| use rustc_apfloat::Float; |
| use rustc_apfloat::ieee::{Double, Half, Quad, Single}; |
| use rustc_hir::Safety; |
| use rustc_hir::def::{DefKind, Namespace}; |
| use rustc_hir::def_id::{CRATE_DEF_INDEX, CrateNum, DefId, LOCAL_CRATE}; |
| use rustc_index::IndexVec; |
| use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; |
| use rustc_middle::middle::dependency_format::Linkage; |
| use rustc_middle::middle::exported_symbols::ExportedSymbol; |
| use rustc_middle::ty::layout::{FnAbiOf, LayoutOf, MaybeResult, TyAndLayout}; |
| use rustc_middle::ty::{self, FloatTy, IntTy, Ty, TyCtxt, UintTy}; |
| use rustc_session::config::CrateType; |
| use rustc_span::{Span, Symbol}; |
| |
| use crate::*; |
| |
| /// Indicates which kind of access is being performed. |
| #[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)] |
| pub enum AccessKind { |
| Read, |
| Write, |
| } |
| |
| /// Gets an instance for a path. |
| /// |
| /// A `None` namespace indicates we are looking for a module. |
| fn try_resolve_did(tcx: TyCtxt<'_>, path: &[&str], namespace: Option<Namespace>) -> Option<DefId> { |
| /// Yield all children of the given item, that have the given name. |
| fn find_children<'tcx: 'a, 'a>( |
| tcx: TyCtxt<'tcx>, |
| item: DefId, |
| name: &'a str, |
| ) -> impl Iterator<Item = DefId> + 'a { |
| tcx.module_children(item) |
| .iter() |
| .filter(move |item| item.ident.name.as_str() == name) |
| .map(move |item| item.res.def_id()) |
| } |
| |
| // Take apart the path: leading crate, a sequence of modules, and potentially a final item. |
| let (&crate_name, path) = path.split_first().expect("paths must have at least one segment"); |
| let (modules, item) = if let Some(namespace) = namespace { |
| let (&item_name, modules) = |
| path.split_last().expect("non-module paths must have at least 2 segments"); |
| (modules, Some((item_name, namespace))) |
| } else { |
| (path, None) |
| }; |
| |
| // There may be more than one crate with this name. We try them all. |
| // (This is particularly relevant when running `std` tests as then there are two `std` crates: |
| // the one in the sysroot and the one locally built by `cargo test`.) |
| // FIXME: can we prefer the one from the sysroot? |
| 'crates: for krate in |
| tcx.crates(()).iter().filter(|&&krate| tcx.crate_name(krate).as_str() == crate_name) |
| { |
| let mut cur_item = DefId { krate: *krate, index: CRATE_DEF_INDEX }; |
| // Go over the modules. |
| for &segment in modules { |
| let Some(next_item) = find_children(tcx, cur_item, segment) |
| .find(|item| tcx.def_kind(item) == DefKind::Mod) |
| else { |
| continue 'crates; |
| }; |
| cur_item = next_item; |
| } |
| // Finally, look up the desired item in this module, if any. |
| match item { |
| Some((item_name, namespace)) => { |
| let Some(item) = find_children(tcx, cur_item, item_name) |
| .find(|item| tcx.def_kind(item).ns() == Some(namespace)) |
| else { |
| continue 'crates; |
| }; |
| return Some(item); |
| } |
| None => { |
| // Just return the module. |
| return Some(cur_item); |
| } |
| } |
| } |
| // Item not found in any of the crates with the right name. |
| None |
| } |
| |
| /// Gets an instance for a path; fails gracefully if the path does not exist. |
| pub fn try_resolve_path<'tcx>( |
| tcx: TyCtxt<'tcx>, |
| path: &[&str], |
| namespace: Namespace, |
| ) -> Option<ty::Instance<'tcx>> { |
| let did = try_resolve_did(tcx, path, Some(namespace))?; |
| Some(ty::Instance::mono(tcx, did)) |
| } |
| |
| /// Gets an instance for a path. |
| #[track_caller] |
| pub fn resolve_path<'tcx>( |
| tcx: TyCtxt<'tcx>, |
| path: &[&str], |
| namespace: Namespace, |
| ) -> ty::Instance<'tcx> { |
| try_resolve_path(tcx, path, namespace) |
| .unwrap_or_else(|| panic!("failed to find required Rust item: {path:?}")) |
| } |
| |
| /// Gets the layout of a type at a path. |
| #[track_caller] |
| pub fn path_ty_layout<'tcx>(cx: &impl LayoutOf<'tcx>, path: &[&str]) -> TyAndLayout<'tcx> { |
| let ty = |
| resolve_path(cx.tcx(), path, Namespace::TypeNS).ty(cx.tcx(), ty::ParamEnv::reveal_all()); |
| cx.layout_of(ty).to_result().ok().unwrap() |
| } |
| |
| /// Call `f` for each exported symbol. |
| pub fn iter_exported_symbols<'tcx>( |
| tcx: TyCtxt<'tcx>, |
| mut f: impl FnMut(CrateNum, DefId) -> InterpResult<'tcx>, |
| ) -> InterpResult<'tcx> { |
| // First, the symbols in the local crate. We can't use `exported_symbols` here as that |
| // skips `#[used]` statics (since `reachable_set` skips them in binary crates). |
| // So we walk all HIR items ourselves instead. |
| let crate_items = tcx.hir_crate_items(()); |
| for def_id in crate_items.definitions() { |
| let exported = tcx.def_kind(def_id).has_codegen_attrs() && { |
| let codegen_attrs = tcx.codegen_fn_attrs(def_id); |
| codegen_attrs.contains_extern_indicator() |
| || codegen_attrs.flags.contains(CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL) |
| || codegen_attrs.flags.contains(CodegenFnAttrFlags::USED) |
| || codegen_attrs.flags.contains(CodegenFnAttrFlags::USED_LINKER) |
| }; |
| if exported { |
| f(LOCAL_CRATE, def_id.into())?; |
| } |
| } |
| |
| // Next, all our dependencies. |
| // `dependency_formats` includes all the transitive informations needed to link a crate, |
| // which is what we need here since we need to dig out `exported_symbols` from all transitive |
| // dependencies. |
| let dependency_formats = tcx.dependency_formats(()); |
| // Find the dependencies of the executable we are running. |
| let dependency_format = dependency_formats |
| .iter() |
| .find(|(crate_type, _)| *crate_type == CrateType::Executable) |
| .expect("interpreting a non-executable crate"); |
| for cnum in dependency_format.1.iter().enumerate().filter_map(|(num, &linkage)| { |
| // We add 1 to the number because that's what rustc also does everywhere it |
| // calls `CrateNum::new`... |
| #[allow(clippy::arithmetic_side_effects)] |
| (linkage != Linkage::NotLinked).then_some(CrateNum::new(num + 1)) |
| }) { |
| // We can ignore `_export_info` here: we are a Rust crate, and everything is exported |
| // from a Rust crate. |
| for &(symbol, _export_info) in tcx.exported_symbols(cnum) { |
| if let ExportedSymbol::NonGeneric(def_id) = symbol { |
| f(cnum, def_id)?; |
| } |
| } |
| } |
| interp_ok(()) |
| } |
| |
| /// Convert a softfloat type to its corresponding hostfloat type. |
| pub trait ToHost { |
| type HostFloat; |
| fn to_host(self) -> Self::HostFloat; |
| } |
| |
| /// Convert a hostfloat type to its corresponding softfloat type. |
| pub trait ToSoft { |
| type SoftFloat; |
| fn to_soft(self) -> Self::SoftFloat; |
| } |
| |
| impl ToHost for rustc_apfloat::ieee::Double { |
| type HostFloat = f64; |
| |
| fn to_host(self) -> Self::HostFloat { |
| f64::from_bits(self.to_bits().try_into().unwrap()) |
| } |
| } |
| |
| impl ToSoft for f64 { |
| type SoftFloat = rustc_apfloat::ieee::Double; |
| |
| fn to_soft(self) -> Self::SoftFloat { |
| Float::from_bits(self.to_bits().into()) |
| } |
| } |
| |
| impl ToHost for rustc_apfloat::ieee::Single { |
| type HostFloat = f32; |
| |
| fn to_host(self) -> Self::HostFloat { |
| f32::from_bits(self.to_bits().try_into().unwrap()) |
| } |
| } |
| |
| impl ToSoft for f32 { |
| type SoftFloat = rustc_apfloat::ieee::Single; |
| |
| fn to_soft(self) -> Self::SoftFloat { |
| Float::from_bits(self.to_bits().into()) |
| } |
| } |
| |
| impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} |
| pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { |
| /// Checks if the given crate/module exists. |
| fn have_module(&self, path: &[&str]) -> bool { |
| try_resolve_did(*self.eval_context_ref().tcx, path, None).is_some() |
| } |
| |
| /// Evaluates the scalar at the specified path. |
| fn eval_path(&self, path: &[&str]) -> MPlaceTy<'tcx> { |
| let this = self.eval_context_ref(); |
| let instance = resolve_path(*this.tcx, path, Namespace::ValueNS); |
| // We don't give a span -- this isn't actually used directly by the program anyway. |
| this.eval_global(instance).unwrap_or_else(|err| { |
| panic!("failed to evaluate required Rust item: {path:?}\n{err:?}") |
| }) |
| } |
| fn eval_path_scalar(&self, path: &[&str]) -> Scalar { |
| let this = self.eval_context_ref(); |
| let val = this.eval_path(path); |
| this.read_scalar(&val) |
| .unwrap_or_else(|err| panic!("failed to read required Rust item: {path:?}\n{err:?}")) |
| } |
| |
| /// Helper function to get a `libc` constant as a `Scalar`. |
| fn eval_libc(&self, name: &str) -> Scalar { |
| if self.eval_context_ref().tcx.sess.target.os == "windows" { |
| panic!( |
| "`libc` crate is not reliably available on Windows targets; Miri should not use it there" |
| ); |
| } |
| self.eval_path_scalar(&["libc", name]) |
| } |
| |
| /// Helper function to get a `libc` constant as an `i32`. |
| fn eval_libc_i32(&self, name: &str) -> i32 { |
| // TODO: Cache the result. |
| self.eval_libc(name).to_i32().unwrap_or_else(|_err| { |
| panic!("required libc item has unexpected type (not `i32`): {name}") |
| }) |
| } |
| |
| /// Helper function to get a `libc` constant as an `u32`. |
| fn eval_libc_u32(&self, name: &str) -> u32 { |
| // TODO: Cache the result. |
| self.eval_libc(name).to_u32().unwrap_or_else(|_err| { |
| panic!("required libc item has unexpected type (not `u32`): {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]) |
| } |
| |
| /// Helper function to get a `windows` constant as a `u32`. |
| fn eval_windows_u32(&self, module: &str, name: &str) -> u32 { |
| // TODO: Cache the result. |
| self.eval_windows(module, name).to_u32().unwrap_or_else(|_err| { |
| panic!("required Windows item has unexpected type (not `u32`): {module}::{name}") |
| }) |
| } |
| |
| /// Helper function to get a `windows` constant as a `u64`. |
| fn eval_windows_u64(&self, module: &str, name: &str) -> u64 { |
| // TODO: Cache the result. |
| self.eval_windows(module, name).to_u64().unwrap_or_else(|_err| { |
| panic!("required Windows item has unexpected type (not `u64`): {module}::{name}") |
| }) |
| } |
| |
| /// Helper function to get the `TyAndLayout` of a `libc` type |
| fn libc_ty_layout(&self, name: &str) -> TyAndLayout<'tcx> { |
| let this = self.eval_context_ref(); |
| if this.tcx.sess.target.os == "windows" { |
| panic!( |
| "`libc` crate is not reliably available on Windows targets; Miri should not use it there" |
| ); |
| } |
| path_ty_layout(this, &["libc", name]) |
| } |
| |
| /// Helper function to get the `TyAndLayout` of a `windows` type |
| fn windows_ty_layout(&self, name: &str) -> TyAndLayout<'tcx> { |
| let this = self.eval_context_ref(); |
| path_ty_layout(this, &["std", "sys", "pal", "windows", "c", name]) |
| } |
| |
| /// Helper function to get `TyAndLayout` of an array that consists of `libc` type. |
| fn libc_array_ty_layout(&self, name: &str, size: u64) -> TyAndLayout<'tcx> { |
| let this = self.eval_context_ref(); |
| let elem_ty_layout = this.libc_ty_layout(name); |
| let array_ty = Ty::new_array(*this.tcx, elem_ty_layout.ty, size); |
| this.layout_of(array_ty).unwrap() |
| } |
| |
| /// Project to the given *named* field (which must be a struct or union type). |
| fn project_field_named<P: Projectable<'tcx, Provenance>>( |
| &self, |
| base: &P, |
| name: &str, |
| ) -> InterpResult<'tcx, P> { |
| let this = self.eval_context_ref(); |
| let adt = base.layout().ty.ty_adt_def().unwrap(); |
| for (idx, field) in adt.non_enum_variant().fields.iter().enumerate() { |
| if field.name.as_str() == name { |
| return this.project_field(base, idx); |
| } |
| } |
| bug!("No field named {} in type {}", name, base.layout().ty); |
| } |
| |
| /// Search if `base` (which must be a struct or union type) contains the `name` field. |
| fn projectable_has_field<P: Projectable<'tcx, Provenance>>( |
| &self, |
| base: &P, |
| name: &str, |
| ) -> bool { |
| let adt = base.layout().ty.ty_adt_def().unwrap(); |
| for field in adt.non_enum_variant().fields.iter() { |
| if field.name.as_str() == name { |
| return true; |
| } |
| } |
| false |
| } |
| |
| /// Write an int of the appropriate size to `dest`. The target type may be signed or unsigned, |
| /// we try to do the right thing anyway. `i128` can fit all integer types except for `u128` so |
| /// this method is fine for almost all integer types. |
| fn write_int( |
| &mut self, |
| i: impl Into<i128>, |
| dest: &impl Writeable<'tcx, Provenance>, |
| ) -> InterpResult<'tcx> { |
| assert!( |
| dest.layout().backend_repr.is_scalar(), |
| "write_int on non-scalar type {}", |
| dest.layout().ty |
| ); |
| let val = if dest.layout().backend_repr.is_signed() { |
| Scalar::from_int(i, dest.layout().size) |
| } else { |
| // `unwrap` can only fail here if `i` is negative |
| Scalar::from_uint(u128::try_from(i.into()).unwrap(), dest.layout().size) |
| }; |
| self.eval_context_mut().write_scalar(val, dest) |
| } |
| |
| /// Write the first N fields of the given place. |
| fn write_int_fields( |
| &mut self, |
| values: &[i128], |
| dest: &impl Writeable<'tcx, Provenance>, |
| ) -> InterpResult<'tcx> { |
| let this = self.eval_context_mut(); |
| for (idx, &val) in values.iter().enumerate() { |
| let field = this.project_field(dest, idx)?; |
| this.write_int(val, &field)?; |
| } |
| interp_ok(()) |
| } |
| |
| /// Write the given fields of the given place. |
| fn write_int_fields_named( |
| &mut self, |
| values: &[(&str, i128)], |
| dest: &impl Writeable<'tcx, Provenance>, |
| ) -> InterpResult<'tcx> { |
| let this = self.eval_context_mut(); |
| for &(name, val) in values.iter() { |
| let field = this.project_field_named(dest, name)?; |
| this.write_int(val, &field)?; |
| } |
| interp_ok(()) |
| } |
| |
| /// Write a 0 of the appropriate size to `dest`. |
| fn write_null(&mut self, dest: &impl Writeable<'tcx, Provenance>) -> InterpResult<'tcx> { |
| self.write_int(0, dest) |
| } |
| |
| /// Test if this pointer equals 0. |
| fn ptr_is_null(&self, ptr: Pointer) -> InterpResult<'tcx, bool> { |
| interp_ok(ptr.addr().bytes() == 0) |
| } |
| |
| /// Generate some random bytes, and write them to `dest`. |
| fn gen_random(&mut self, ptr: Pointer, len: u64) -> InterpResult<'tcx> { |
| // Some programs pass in a null pointer and a length of 0 |
| // to their platform's random-generation function (e.g. getrandom()) |
| // on Linux. For compatibility with these programs, we don't perform |
| // any additional checks - it's okay if the pointer is invalid, |
| // since we wouldn't actually be writing to it. |
| if len == 0 { |
| return interp_ok(()); |
| } |
| let this = self.eval_context_mut(); |
| |
| let mut data = vec![0; usize::try_from(len).unwrap()]; |
| |
| if this.machine.communicate() { |
| // Fill the buffer using the host's rng. |
| getrandom::getrandom(&mut data) |
| .map_err(|err| err_unsup_format!("host getrandom failed: {}", err))?; |
| } else { |
| let rng = this.machine.rng.get_mut(); |
| rng.fill_bytes(&mut data); |
| } |
| |
| this.write_bytes_ptr(ptr, data.iter().copied()) |
| } |
| |
| /// Call a function: Push the stack frame and pass the arguments. |
| /// For now, arguments must be scalars (so that the caller does not have to know the layout). |
| /// |
| /// If you do not provide a return place, a dangling zero-sized place will be created |
| /// for your convenience. |
| fn call_function( |
| &mut self, |
| f: ty::Instance<'tcx>, |
| caller_abi: ExternAbi, |
| args: &[ImmTy<'tcx>], |
| dest: Option<&MPlaceTy<'tcx>>, |
| stack_pop: StackPopCleanup, |
| ) -> InterpResult<'tcx> { |
| let this = self.eval_context_mut(); |
| |
| // Get MIR. |
| let mir = this.load_mir(f.def, None)?; |
| let dest = match dest { |
| Some(dest) => dest.clone(), |
| None => MPlaceTy::fake_alloc_zst(this.layout_of(mir.return_ty())?), |
| }; |
| |
| // Construct a function pointer type representing the caller perspective. |
| let sig = this.tcx.mk_fn_sig( |
| args.iter().map(|a| a.layout.ty), |
| dest.layout.ty, |
| /*c_variadic*/ false, |
| Safety::Safe, |
| caller_abi, |
| ); |
| let caller_fn_abi = this.fn_abi_of_fn_ptr(ty::Binder::dummy(sig), ty::List::empty())?; |
| |
| this.init_stack_frame( |
| f, |
| mir, |
| caller_fn_abi, |
| &args.iter().map(|a| FnArg::Copy(a.clone().into())).collect::<Vec<_>>(), |
| /*with_caller_location*/ false, |
| &dest, |
| stack_pop, |
| ) |
| } |
| |
| /// Visits the memory covered by `place`, sensitive to freezing: the 2nd parameter |
| /// of `action` will be true if this is frozen, false if this is in an `UnsafeCell`. |
| /// The range is relative to `place`. |
| fn visit_freeze_sensitive( |
| &self, |
| place: &MPlaceTy<'tcx>, |
| size: Size, |
| mut action: impl FnMut(AllocRange, bool) -> InterpResult<'tcx>, |
| ) -> InterpResult<'tcx> { |
| let this = self.eval_context_ref(); |
| trace!("visit_frozen(place={:?}, size={:?})", *place, size); |
| debug_assert_eq!( |
| size, |
| this.size_and_align_of_mplace(place)? |
| .map(|(size, _)| size) |
| .unwrap_or_else(|| place.layout.size) |
| ); |
| // Store how far we proceeded into the place so far. Everything to the left of |
| // this offset has already been handled, in the sense that the frozen parts |
| // have had `action` called on them. |
| let start_addr = place.ptr().addr(); |
| let mut cur_addr = start_addr; |
| // Called when we detected an `UnsafeCell` at the given offset and size. |
| // Calls `action` and advances `cur_ptr`. |
| let mut unsafe_cell_action = |unsafe_cell_ptr: &Pointer, unsafe_cell_size: Size| { |
| // We assume that we are given the fields in increasing offset order, |
| // and nothing else changes. |
| let unsafe_cell_addr = unsafe_cell_ptr.addr(); |
| assert!(unsafe_cell_addr >= cur_addr); |
| let frozen_size = unsafe_cell_addr - cur_addr; |
| // Everything between the cur_ptr and this `UnsafeCell` is frozen. |
| if frozen_size != Size::ZERO { |
| action(alloc_range(cur_addr - start_addr, frozen_size), /*frozen*/ true)?; |
| } |
| cur_addr += frozen_size; |
| // This `UnsafeCell` is NOT frozen. |
| if unsafe_cell_size != Size::ZERO { |
| action( |
| alloc_range(cur_addr - start_addr, unsafe_cell_size), |
| /*frozen*/ false, |
| )?; |
| } |
| cur_addr += unsafe_cell_size; |
| // Done |
| interp_ok(()) |
| }; |
| // Run a visitor |
| { |
| let mut visitor = UnsafeCellVisitor { |
| ecx: this, |
| unsafe_cell_action: |place| { |
| trace!("unsafe_cell_action on {:?}", place.ptr()); |
| // We need a size to go on. |
| let unsafe_cell_size = this |
| .size_and_align_of_mplace(place)? |
| .map(|(size, _)| size) |
| // for extern types, just cover what we can |
| .unwrap_or_else(|| place.layout.size); |
| // Now handle this `UnsafeCell`, unless it is empty. |
| if unsafe_cell_size != Size::ZERO { |
| unsafe_cell_action(&place.ptr(), unsafe_cell_size) |
| } else { |
| interp_ok(()) |
| } |
| }, |
| }; |
| visitor.visit_value(place)?; |
| } |
| // The part between the end_ptr and the end of the place is also frozen. |
| // So pretend there is a 0-sized `UnsafeCell` at the end. |
| unsafe_cell_action(&place.ptr().wrapping_offset(size, this), Size::ZERO)?; |
| // Done! |
| return interp_ok(()); |
| |
| /// Visiting the memory covered by a `MemPlace`, being aware of |
| /// whether we are inside an `UnsafeCell` or not. |
| struct UnsafeCellVisitor<'ecx, 'tcx, F> |
| where |
| F: FnMut(&MPlaceTy<'tcx>) -> InterpResult<'tcx>, |
| { |
| ecx: &'ecx MiriInterpCx<'tcx>, |
| unsafe_cell_action: F, |
| } |
| |
| impl<'ecx, 'tcx, F> ValueVisitor<'tcx, MiriMachine<'tcx>> for UnsafeCellVisitor<'ecx, 'tcx, F> |
| where |
| F: FnMut(&MPlaceTy<'tcx>) -> InterpResult<'tcx>, |
| { |
| type V = MPlaceTy<'tcx>; |
| |
| #[inline(always)] |
| fn ecx(&self) -> &MiriInterpCx<'tcx> { |
| self.ecx |
| } |
| |
| fn aggregate_field_iter( |
| memory_index: &IndexVec<FieldIdx, u32>, |
| ) -> impl Iterator<Item = FieldIdx> + 'static { |
| let inverse_memory_index = memory_index.invert_bijective_mapping(); |
| inverse_memory_index.into_iter() |
| } |
| |
| // Hook to detect `UnsafeCell`. |
| fn visit_value(&mut self, v: &MPlaceTy<'tcx>) -> InterpResult<'tcx> { |
| trace!("UnsafeCellVisitor: {:?} {:?}", *v, v.layout.ty); |
| let is_unsafe_cell = match v.layout.ty.kind() { |
| ty::Adt(adt, _) => |
| Some(adt.did()) == self.ecx.tcx.lang_items().unsafe_cell_type(), |
| _ => false, |
| }; |
| if is_unsafe_cell { |
| // We do not have to recurse further, this is an `UnsafeCell`. |
| (self.unsafe_cell_action)(v) |
| } else if self.ecx.type_is_freeze(v.layout.ty) { |
| // This is `Freeze`, there cannot be an `UnsafeCell` |
| interp_ok(()) |
| } else if matches!(v.layout.fields, FieldsShape::Union(..)) { |
| // A (non-frozen) union. We fall back to whatever the type says. |
| (self.unsafe_cell_action)(v) |
| } else if matches!(v.layout.ty.kind(), ty::Dynamic(_, _, ty::DynStar)) { |
| // This needs to read the vtable pointer to proceed type-driven, but we don't |
| // want to reentrantly read from memory here. |
| (self.unsafe_cell_action)(v) |
| } else { |
| // We want to not actually read from memory for this visit. So, before |
| // walking this value, we have to make sure it is not a |
| // `Variants::Multiple`. |
| match v.layout.variants { |
| Variants::Multiple { .. } => { |
| // A multi-variant enum, or coroutine, or so. |
| // Treat this like a union: without reading from memory, |
| // we cannot determine the variant we are in. Reading from |
| // memory would be subject to Stacked Borrows rules, leading |
| // to all sorts of "funny" recursion. |
| // We only end up here if the type is *not* freeze, so we just call the |
| // `UnsafeCell` action. |
| (self.unsafe_cell_action)(v) |
| } |
| Variants::Single { .. } => { |
| // Proceed further, try to find where exactly that `UnsafeCell` |
| // is hiding. |
| self.walk_value(v) |
| } |
| } |
| } |
| } |
| |
| fn visit_union( |
| &mut self, |
| _v: &MPlaceTy<'tcx>, |
| _fields: NonZero<usize>, |
| ) -> InterpResult<'tcx> { |
| bug!("we should have already handled unions in `visit_value`") |
| } |
| } |
| } |
| |
| /// Helper function used inside the shims of foreign functions to check that isolation is |
| /// disabled. It returns an error using the `name` of the foreign function if this is not the |
| /// case. |
| fn check_no_isolation(&self, name: &str) -> InterpResult<'tcx> { |
| if !self.eval_context_ref().machine.communicate() { |
| self.reject_in_isolation(name, RejectOpWith::Abort)?; |
| } |
| interp_ok(()) |
| } |
| |
| /// Helper function used inside the shims of foreign functions which reject the op |
| /// when isolation is enabled. It is used to print a warning/backtrace about the rejection. |
| fn reject_in_isolation(&self, op_name: &str, reject_with: RejectOpWith) -> InterpResult<'tcx> { |
| let this = self.eval_context_ref(); |
| match reject_with { |
| RejectOpWith::Abort => isolation_abort_error(op_name), |
| RejectOpWith::WarningWithoutBacktrace => { |
| // This exists to reduce verbosity; make sure we emit the warning at most once per |
| // operation. |
| static EMITTED_WARNINGS: Mutex<BTreeSet<String>> = Mutex::new(BTreeSet::new()); |
| |
| let mut emitted_warnings = EMITTED_WARNINGS.lock().unwrap(); |
| if !emitted_warnings.contains(op_name) { |
| // First time we are seeing this. |
| emitted_warnings.insert(op_name.to_owned()); |
| this.tcx |
| .dcx() |
| .warn(format!("{op_name} was made to return an error due to isolation")); |
| } |
| interp_ok(()) |
| } |
| RejectOpWith::Warning => { |
| this.emit_diagnostic(NonHaltingDiagnostic::RejectedIsolatedOp(op_name.to_string())); |
| interp_ok(()) |
| } |
| RejectOpWith::NoWarning => interp_ok(()), // no warning |
| } |
| } |
| |
| /// Helper function used inside the shims of foreign functions to assert that the target OS |
| /// is `target_os`. It panics showing a message with the `name` of the foreign function |
| /// if this is not the case. |
| fn assert_target_os(&self, target_os: &str, name: &str) { |
| assert_eq!( |
| self.eval_context_ref().tcx.sess.target.os, |
| target_os, |
| "`{name}` is only available on the `{target_os}` target OS", |
| ) |
| } |
| |
| /// Helper function used inside the shims of foreign functions to assert that the target OS |
| /// is part of the UNIX family. It panics showing a message with the `name` of the foreign function |
| /// if this is not the case. |
| fn assert_target_os_is_unix(&self, name: &str) { |
| assert!(self.target_os_is_unix(), "`{name}` is only available for unix targets",); |
| } |
| |
| fn target_os_is_unix(&self) -> bool { |
| self.eval_context_ref().tcx.sess.target.families.iter().any(|f| f == "unix") |
| } |
| |
| /// Dereference a pointer operand to a place using `layout` instead of the pointer's declared type |
| fn deref_pointer_as( |
| &self, |
| op: &impl Projectable<'tcx, Provenance>, |
| layout: TyAndLayout<'tcx>, |
| ) -> InterpResult<'tcx, MPlaceTy<'tcx>> { |
| let this = self.eval_context_ref(); |
| let ptr = this.read_pointer(op)?; |
| interp_ok(this.ptr_to_mplace(ptr, layout)) |
| } |
| |
| /// Calculates the MPlaceTy given the offset and layout of an access on an operand |
| fn deref_pointer_and_offset( |
| &self, |
| op: &impl Projectable<'tcx, Provenance>, |
| offset: u64, |
| base_layout: TyAndLayout<'tcx>, |
| value_layout: TyAndLayout<'tcx>, |
| ) -> InterpResult<'tcx, MPlaceTy<'tcx>> { |
| let this = self.eval_context_ref(); |
| let op_place = this.deref_pointer_as(op, base_layout)?; |
| let offset = Size::from_bytes(offset); |
| |
| // Ensure that the access is within bounds. |
| assert!(base_layout.size >= offset + value_layout.size); |
| let value_place = op_place.offset(offset, value_layout, this)?; |
| interp_ok(value_place) |
| } |
| |
| fn deref_pointer_and_read( |
| &self, |
| op: &impl Projectable<'tcx, Provenance>, |
| offset: u64, |
| base_layout: TyAndLayout<'tcx>, |
| value_layout: TyAndLayout<'tcx>, |
| ) -> InterpResult<'tcx, Scalar> { |
| let this = self.eval_context_ref(); |
| let value_place = this.deref_pointer_and_offset(op, offset, base_layout, value_layout)?; |
| this.read_scalar(&value_place) |
| } |
| |
| fn deref_pointer_and_write( |
| &mut self, |
| op: &impl Projectable<'tcx, Provenance>, |
| offset: u64, |
| value: impl Into<Scalar>, |
| base_layout: TyAndLayout<'tcx>, |
| value_layout: TyAndLayout<'tcx>, |
| ) -> InterpResult<'tcx, ()> { |
| let this = self.eval_context_mut(); |
| let value_place = this.deref_pointer_and_offset(op, offset, base_layout, value_layout)?; |
| this.write_scalar(value, &value_place) |
| } |
| |
| /// Parse a `timespec` struct and return it as a `std::time::Duration`. It returns `None` |
| /// if the value in the `timespec` struct is invalid. Some libc functions will return |
| /// `EINVAL` in this case. |
| fn read_timespec(&mut self, tp: &MPlaceTy<'tcx>) -> InterpResult<'tcx, Option<Duration>> { |
| let this = self.eval_context_mut(); |
| let seconds_place = this.project_field(tp, 0)?; |
| let seconds_scalar = this.read_scalar(&seconds_place)?; |
| let seconds = seconds_scalar.to_target_isize(this)?; |
| let nanoseconds_place = this.project_field(tp, 1)?; |
| let nanoseconds_scalar = this.read_scalar(&nanoseconds_place)?; |
| let nanoseconds = nanoseconds_scalar.to_target_isize(this)?; |
| |
| interp_ok( |
| try { |
| // tv_sec must be non-negative. |
| let seconds: u64 = seconds.try_into().ok()?; |
| // tv_nsec must be non-negative. |
| let nanoseconds: u32 = nanoseconds.try_into().ok()?; |
| if nanoseconds >= 1_000_000_000 { |
| // tv_nsec must not be greater than 999,999,999. |
| None? |
| } |
| Duration::new(seconds, nanoseconds) |
| }, |
| ) |
| } |
| |
| /// Read bytes from a byte slice. |
| fn read_byte_slice<'a>(&'a self, slice: &ImmTy<'tcx>) -> InterpResult<'tcx, &'a [u8]> |
| where |
| 'tcx: 'a, |
| { |
| let this = self.eval_context_ref(); |
| let (ptr, len) = slice.to_scalar_pair(); |
| let ptr = ptr.to_pointer(this)?; |
| let len = len.to_target_usize(this)?; |
| let bytes = this.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?; |
| interp_ok(bytes) |
| } |
| |
| /// Read a sequence of bytes until the first null terminator. |
| fn read_c_str<'a>(&'a self, ptr: Pointer) -> InterpResult<'tcx, &'a [u8]> |
| where |
| 'tcx: 'a, |
| { |
| let this = self.eval_context_ref(); |
| let size1 = Size::from_bytes(1); |
| |
| // Step 1: determine the length. |
| let mut len = Size::ZERO; |
| loop { |
| // FIXME: We are re-getting the allocation each time around the loop. |
| // Would be nice if we could somehow "extend" an existing AllocRange. |
| let alloc = this.get_ptr_alloc(ptr.wrapping_offset(len, this), size1)?.unwrap(); // not a ZST, so we will get a result |
| let byte = alloc.read_integer(alloc_range(Size::ZERO, size1))?.to_u8()?; |
| if byte == 0 { |
| break; |
| } else { |
| len += size1; |
| } |
| } |
| |
| // Step 2: get the bytes. |
| this.read_bytes_ptr_strip_provenance(ptr, len) |
| } |
| |
| /// Helper function to write a sequence of bytes with an added null-terminator, which is what |
| /// the Unix APIs usually handle. This function returns `Ok((false, length))` without trying |
| /// to write if `size` is not large enough to fit the contents of `c_str` plus a null |
| /// terminator. It returns `Ok((true, length))` if the writing process was successful. The |
| /// string length returned does include the null terminator. |
| fn write_c_str( |
| &mut self, |
| c_str: &[u8], |
| ptr: Pointer, |
| size: u64, |
| ) -> InterpResult<'tcx, (bool, u64)> { |
| // If `size` is smaller or equal than `bytes.len()`, writing `bytes` plus the required null |
| // terminator to memory using the `ptr` pointer would cause an out-of-bounds access. |
| let string_length = u64::try_from(c_str.len()).unwrap(); |
| let string_length = string_length.strict_add(1); |
| if size < string_length { |
| return interp_ok((false, string_length)); |
| } |
| self.eval_context_mut() |
| .write_bytes_ptr(ptr, c_str.iter().copied().chain(iter::once(0u8)))?; |
| interp_ok((true, string_length)) |
| } |
| |
| /// Helper function to read a sequence of unsigned integers of the given size and alignment |
| /// until the first null terminator. |
| fn read_c_str_with_char_size<T>( |
| &self, |
| mut ptr: Pointer, |
| size: Size, |
| align: Align, |
| ) -> InterpResult<'tcx, Vec<T>> |
| where |
| T: TryFrom<u128>, |
| <T as TryFrom<u128>>::Error: std::fmt::Debug, |
| { |
| assert_ne!(size, Size::ZERO); |
| |
| let this = self.eval_context_ref(); |
| |
| this.check_ptr_align(ptr, align)?; |
| |
| let mut wchars = Vec::new(); |
| loop { |
| // FIXME: We are re-getting the allocation each time around the loop. |
| // Would be nice if we could somehow "extend" an existing AllocRange. |
| let alloc = this.get_ptr_alloc(ptr, size)?.unwrap(); // not a ZST, so we will get a result |
| let wchar_int = alloc.read_integer(alloc_range(Size::ZERO, size))?.to_bits(size)?; |
| if wchar_int == 0 { |
| break; |
| } else { |
| wchars.push(wchar_int.try_into().unwrap()); |
| ptr = ptr.wrapping_offset(size, this); |
| } |
| } |
| |
| interp_ok(wchars) |
| } |
| |
| /// Read a sequence of u16 until the first null terminator. |
| fn read_wide_str(&self, ptr: Pointer) -> InterpResult<'tcx, Vec<u16>> { |
| self.read_c_str_with_char_size(ptr, Size::from_bytes(2), Align::from_bytes(2).unwrap()) |
| } |
| |
| /// Helper function to write a sequence of u16 with an added 0x0000-terminator, which is what |
| /// the Windows APIs usually handle. This function returns `Ok((false, length))` without trying |
| /// to write if `size` is not large enough to fit the contents of `os_string` plus a null |
| /// terminator. It returns `Ok((true, length))` if the writing process was successful. The |
| /// string length returned does include the null terminator. Length is measured in units of |
| /// `u16.` |
| fn write_wide_str( |
| &mut self, |
| wide_str: &[u16], |
| ptr: Pointer, |
| size: u64, |
| ) -> InterpResult<'tcx, (bool, u64)> { |
| // If `size` is smaller or equal than `bytes.len()`, writing `bytes` plus the required |
| // 0x0000 terminator to memory would cause an out-of-bounds access. |
| let string_length = u64::try_from(wide_str.len()).unwrap(); |
| let string_length = string_length.strict_add(1); |
| if size < string_length { |
| return interp_ok((false, string_length)); |
| } |
| |
| // Store the UTF-16 string. |
| let size2 = Size::from_bytes(2); |
| let this = self.eval_context_mut(); |
| this.check_ptr_align(ptr, Align::from_bytes(2).unwrap())?; |
| let mut alloc = this.get_ptr_alloc_mut(ptr, size2 * string_length)?.unwrap(); // not a ZST, so we will get a result |
| for (offset, wchar) in wide_str.iter().copied().chain(iter::once(0x0000)).enumerate() { |
| let offset = u64::try_from(offset).unwrap(); |
| alloc.write_scalar(alloc_range(size2 * offset, size2), Scalar::from_u16(wchar))?; |
| } |
| interp_ok((true, string_length)) |
| } |
| |
| /// Read a sequence of wchar_t until the first null terminator. |
| /// Always returns a `Vec<u32>` no matter the size of `wchar_t`. |
| fn read_wchar_t_str(&self, ptr: Pointer) -> InterpResult<'tcx, Vec<u32>> { |
| let this = self.eval_context_ref(); |
| let wchar_t = if this.tcx.sess.target.os == "windows" { |
| // We don't have libc on Windows so we have to hard-code the type ourselves. |
| this.machine.layouts.u16 |
| } else { |
| this.libc_ty_layout("wchar_t") |
| }; |
| self.read_c_str_with_char_size(ptr, wchar_t.size, wchar_t.align.abi) |
| } |
| |
| /// Check that the ABI is what we expect. |
| fn check_abi<'a>(&self, abi: ExternAbi, exp_abi: ExternAbi) -> InterpResult<'a, ()> { |
| if abi != exp_abi { |
| throw_ub_format!( |
| "calling a function with ABI {} using caller ABI {}", |
| exp_abi.name(), |
| abi.name() |
| ) |
| } |
| interp_ok(()) |
| } |
| |
| fn frame_in_std(&self) -> bool { |
| let this = self.eval_context_ref(); |
| let frame = this.frame(); |
| // Make an attempt to get at the instance of the function this is inlined from. |
| let instance: Option<_> = try { |
| let scope = frame.current_source_info()?.scope; |
| let inlined_parent = frame.body().source_scopes[scope].inlined_parent_scope?; |
| let source = &frame.body().source_scopes[inlined_parent]; |
| source.inlined.expect("inlined_parent_scope points to scope without inline info").0 |
| }; |
| // Fall back to the instance of the function itself. |
| let instance = instance.unwrap_or(frame.instance()); |
| // Now check the crate it is in. We could try to be clever here and e.g. check if this is |
| // the same crate as `start_fn`, but that would not work for running std tests in Miri, so |
| // we'd need some more hacks anyway. So we just check the name of the crate. If someone |
| // calls their crate `std` then we'll just let them keep the pieces. |
| let frame_crate = this.tcx.def_path(instance.def_id()).krate; |
| let crate_name = this.tcx.crate_name(frame_crate); |
| let crate_name = crate_name.as_str(); |
| // On miri-test-libstd, the name of the crate is different. |
| crate_name == "std" || crate_name == "std_miri_test" |
| } |
| |
| fn check_abi_and_shim_symbol_clash( |
| &mut self, |
| abi: ExternAbi, |
| exp_abi: ExternAbi, |
| link_name: Symbol, |
| ) -> InterpResult<'tcx, ()> { |
| self.check_abi(abi, exp_abi)?; |
| if let Some((body, instance)) = self.eval_context_mut().lookup_exported_symbol(link_name)? { |
| // If compiler-builtins is providing the symbol, then don't treat it as a clash. |
| // We'll use our built-in implementation in `emulate_foreign_item_inner` for increased |
| // performance. Note that this means we won't catch any undefined behavior in |
| // compiler-builtins when running other crates, but Miri can still be run on |
| // compiler-builtins itself (or any crate that uses it as a normal dependency) |
| if self.eval_context_ref().tcx.is_compiler_builtins(instance.def_id().krate) { |
| return interp_ok(()); |
| } |
| |
| throw_machine_stop!(TerminationInfo::SymbolShimClashing { |
| link_name, |
| span: body.span.data(), |
| }) |
| } |
| interp_ok(()) |
| } |
| |
| fn check_shim<'a, const N: usize>( |
| &mut self, |
| abi: ExternAbi, |
| exp_abi: ExternAbi, |
| link_name: Symbol, |
| args: &'a [OpTy<'tcx>], |
| ) -> InterpResult<'tcx, &'a [OpTy<'tcx>; N]> |
| where |
| &'a [OpTy<'tcx>; N]: TryFrom<&'a [OpTy<'tcx>]>, |
| { |
| self.check_abi_and_shim_symbol_clash(abi, exp_abi, link_name)?; |
| check_arg_count(args) |
| } |
| |
| /// Mark a machine allocation that was just created as immutable. |
| fn mark_immutable(&mut self, mplace: &MPlaceTy<'tcx>) { |
| let this = self.eval_context_mut(); |
| // This got just allocated, so there definitely is a pointer here. |
| let provenance = mplace.ptr().into_pointer_or_addr().unwrap().provenance; |
| this.alloc_mark_immutable(provenance.get_alloc_id().unwrap()).unwrap(); |
| } |
| |
| /// Converts `src` from floating point to integer type `dest_ty` |
| /// after rounding with mode `round`. |
| /// Returns `None` if `f` is NaN or out of range. |
| fn float_to_int_checked( |
| &self, |
| src: &ImmTy<'tcx>, |
| cast_to: TyAndLayout<'tcx>, |
| round: rustc_apfloat::Round, |
| ) -> InterpResult<'tcx, Option<ImmTy<'tcx>>> { |
| let this = self.eval_context_ref(); |
| |
| fn float_to_int_inner<'tcx, F: rustc_apfloat::Float>( |
| this: &MiriInterpCx<'tcx>, |
| src: F, |
| cast_to: TyAndLayout<'tcx>, |
| round: rustc_apfloat::Round, |
| ) -> (Scalar, rustc_apfloat::Status) { |
| let int_size = cast_to.layout.size; |
| match cast_to.ty.kind() { |
| // Unsigned |
| ty::Uint(_) => { |
| let res = src.to_u128_r(int_size.bits_usize(), round, &mut false); |
| (Scalar::from_uint(res.value, int_size), res.status) |
| } |
| // Signed |
| ty::Int(_) => { |
| let res = src.to_i128_r(int_size.bits_usize(), round, &mut false); |
| (Scalar::from_int(res.value, int_size), res.status) |
| } |
| // Nothing else |
| _ => |
| span_bug!( |
| this.cur_span(), |
| "attempted float-to-int conversion with non-int output type {}", |
| cast_to.ty, |
| ), |
| } |
| } |
| |
| let ty::Float(fty) = src.layout.ty.kind() else { |
| bug!("float_to_int_checked: non-float input type {}", src.layout.ty) |
| }; |
| |
| let (val, status) = match fty { |
| FloatTy::F16 => |
| float_to_int_inner::<Half>(this, src.to_scalar().to_f16()?, cast_to, round), |
| FloatTy::F32 => |
| float_to_int_inner::<Single>(this, src.to_scalar().to_f32()?, cast_to, round), |
| FloatTy::F64 => |
| float_to_int_inner::<Double>(this, src.to_scalar().to_f64()?, cast_to, round), |
| FloatTy::F128 => |
| float_to_int_inner::<Quad>(this, src.to_scalar().to_f128()?, cast_to, round), |
| }; |
| |
| if status.intersects( |
| rustc_apfloat::Status::INVALID_OP |
| | rustc_apfloat::Status::OVERFLOW |
| | rustc_apfloat::Status::UNDERFLOW, |
| ) { |
| // Floating point value is NaN (flagged with INVALID_OP) or outside the range |
| // of values of the integer type (flagged with OVERFLOW or UNDERFLOW). |
| interp_ok(None) |
| } else { |
| // Floating point value can be represented by the integer type after rounding. |
| // The INEXACT flag is ignored on purpose to allow rounding. |
| interp_ok(Some(ImmTy::from_scalar(val, cast_to))) |
| } |
| } |
| |
| /// Returns an integer type that is twice wide as `ty` |
| fn get_twice_wide_int_ty(&self, ty: Ty<'tcx>) -> Ty<'tcx> { |
| let this = self.eval_context_ref(); |
| match ty.kind() { |
| // Unsigned |
| ty::Uint(UintTy::U8) => this.tcx.types.u16, |
| ty::Uint(UintTy::U16) => this.tcx.types.u32, |
| ty::Uint(UintTy::U32) => this.tcx.types.u64, |
| ty::Uint(UintTy::U64) => this.tcx.types.u128, |
| // Signed |
| ty::Int(IntTy::I8) => this.tcx.types.i16, |
| ty::Int(IntTy::I16) => this.tcx.types.i32, |
| ty::Int(IntTy::I32) => this.tcx.types.i64, |
| ty::Int(IntTy::I64) => this.tcx.types.i128, |
| _ => span_bug!(this.cur_span(), "unexpected type: {ty:?}"), |
| } |
| } |
| |
| /// Checks that target feature `target_feature` is enabled. |
| /// |
| /// If not enabled, emits an UB error that states that the feature is |
| /// required by `intrinsic`. |
| fn expect_target_feature_for_intrinsic( |
| &self, |
| intrinsic: Symbol, |
| target_feature: &str, |
| ) -> InterpResult<'tcx, ()> { |
| let this = self.eval_context_ref(); |
| if !this.tcx.sess.unstable_target_features.contains(&Symbol::intern(target_feature)) { |
| throw_ub_format!( |
| "attempted to call intrinsic `{intrinsic}` that requires missing target feature {target_feature}" |
| ); |
| } |
| interp_ok(()) |
| } |
| |
| /// Lookup an array of immediates stored as a linker section of name `name`. |
| fn lookup_link_section(&mut self, name: &str) -> InterpResult<'tcx, Vec<ImmTy<'tcx>>> { |
| let this = self.eval_context_mut(); |
| let tcx = this.tcx.tcx; |
| |
| let mut array = vec![]; |
| |
| iter_exported_symbols(tcx, |_cnum, def_id| { |
| let attrs = tcx.codegen_fn_attrs(def_id); |
| let Some(link_section) = attrs.link_section else { |
| return interp_ok(()); |
| }; |
| if link_section.as_str() == name { |
| let instance = ty::Instance::mono(tcx, def_id); |
| let const_val = this.eval_global(instance).unwrap_or_else(|err| { |
| panic!( |
| "failed to evaluate static in required link_section: {def_id:?}\n{err:?}" |
| ) |
| }); |
| let val = this.read_immediate(&const_val)?; |
| array.push(val); |
| } |
| interp_ok(()) |
| })?; |
| |
| interp_ok(array) |
| } |
| } |
| |
| impl<'tcx> MiriMachine<'tcx> { |
| /// Get the current span in the topmost function which is workspace-local and not |
| /// `#[track_caller]`. |
| /// This function is backed by a cache, and can be assumed to be very fast. |
| /// It will work even when the stack is empty. |
| pub fn current_span(&self) -> Span { |
| self.threads.active_thread_ref().current_span() |
| } |
| |
| /// Returns the span of the *caller* of the current operation, again |
| /// walking down the stack to find the closest frame in a local crate, if the caller of the |
| /// current operation is not in a local crate. |
| /// This is useful when we are processing something which occurs on function-entry and we want |
| /// to point at the call to the function, not the function definition generally. |
| pub fn caller_span(&self) -> Span { |
| // We need to go down at least to the caller (len - 2), or however |
| // far we have to go to find a frame in a local crate which is also not #[track_caller]. |
| let frame_idx = self.top_user_relevant_frame().unwrap(); |
| let frame_idx = cmp::min(frame_idx, self.stack().len().saturating_sub(2)); |
| self.stack()[frame_idx].current_span() |
| } |
| |
| fn stack(&self) -> &[Frame<'tcx, Provenance, machine::FrameExtra<'tcx>>] { |
| self.threads.active_thread_stack() |
| } |
| |
| fn top_user_relevant_frame(&self) -> Option<usize> { |
| self.threads.active_thread_ref().top_user_relevant_frame() |
| } |
| |
| /// This is the source of truth for the `is_user_relevant` flag in our `FrameExtra`. |
| pub fn is_user_relevant(&self, frame: &Frame<'tcx, Provenance>) -> bool { |
| let def_id = frame.instance().def_id(); |
| (def_id.is_local() || self.local_crates.contains(&def_id.krate)) |
| && !frame.instance().def.requires_caller_location(self.tcx) |
| } |
| } |
| |
| /// Check that the number of args is what we expect. |
| pub fn check_arg_count<'a, 'tcx, const N: usize>( |
| args: &'a [OpTy<'tcx>], |
| ) -> InterpResult<'tcx, &'a [OpTy<'tcx>; N]> |
| where |
| &'a [OpTy<'tcx>; N]: TryFrom<&'a [OpTy<'tcx>]>, |
| { |
| if let Ok(ops) = args.try_into() { |
| return interp_ok(ops); |
| } |
| throw_ub_format!("incorrect number of arguments: got {}, expected {}", args.len(), N) |
| } |
| |
| /// Check that the number of args is at least the minumim what we expect. |
| pub fn check_min_arg_count<'a, 'tcx, const N: usize>( |
| name: &'a str, |
| args: &'a [OpTy<'tcx>], |
| ) -> InterpResult<'tcx, &'a [OpTy<'tcx>; N]> { |
| if let Some((ops, _)) = args.split_first_chunk() { |
| return interp_ok(ops); |
| } |
| throw_ub_format!( |
| "incorrect number of arguments for `{name}`: got {}, expected at least {}", |
| args.len(), |
| N |
| ) |
| } |
| |
| pub fn isolation_abort_error<'tcx>(name: &str) -> InterpResult<'tcx> { |
| throw_machine_stop!(TerminationInfo::UnsupportedInIsolation(format!( |
| "{name} not available when isolation is enabled", |
| ))) |
| } |
| |
| /// Retrieve the list of local crates that should have been passed by cargo-miri in |
| /// MIRI_LOCAL_CRATES and turn them into `CrateNum`s. |
| pub fn get_local_crates(tcx: TyCtxt<'_>) -> Vec<CrateNum> { |
| // Convert the local crate names from the passed-in config into CrateNums so that they can |
| // be looked up quickly during execution |
| let local_crate_names = std::env::var("MIRI_LOCAL_CRATES") |
| .map(|crates| crates.split(',').map(|krate| krate.to_string()).collect::<Vec<_>>()) |
| .unwrap_or_default(); |
| let mut local_crates = Vec::new(); |
| for &crate_num in tcx.crates(()) { |
| let name = tcx.crate_name(crate_num); |
| let name = name.as_str(); |
| if local_crate_names.iter().any(|local_name| local_name == name) { |
| local_crates.push(crate_num); |
| } |
| } |
| local_crates |
| } |
| |
| pub(crate) fn bool_to_simd_element(b: bool, size: Size) -> Scalar { |
| // SIMD uses all-1 as pattern for "true". In two's complement, |
| // -1 has all its bits set to one and `from_int` will truncate or |
| // sign-extend it to `size` as required. |
| let val = if b { -1 } else { 0 }; |
| Scalar::from_int(val, size) |
| } |
| |
| pub(crate) fn simd_element_to_bool(elem: ImmTy<'_>) -> InterpResult<'_, bool> { |
| let val = elem.to_scalar().to_int(elem.layout.size)?; |
| interp_ok(match val { |
| 0 => false, |
| -1 => true, |
| _ => throw_ub_format!("each element of a SIMD mask must be all-0-bits or all-1-bits"), |
| }) |
| } |
| |
| /// Check whether an operation that writes to a target buffer was successful. |
| /// Accordingly select return value. |
| /// Local helper function to be used in Windows shims. |
| pub(crate) fn windows_check_buffer_size((success, len): (bool, u64)) -> u32 { |
| if success { |
| // If the function succeeds, the return value is the number of characters stored in the target buffer, |
| // not including the terminating null character. |
| u32::try_from(len.strict_sub(1)).unwrap() |
| } else { |
| // If the target buffer was not large enough to hold the data, the return value is the buffer size, in characters, |
| // required to hold the string and its terminating null character. |
| u32::try_from(len).unwrap() |
| } |
| } |