X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=src%2Fhelpers.rs;h=7f99aa1997068ae3099f0d01112423af4b71e488;hb=879000b133aed8cc1893c84eb5319b491a4756d9;hp=9bc0279aa455e06ffe8dde05354b220046d9fcd2;hpb=fbbca59de7873ebad6452c576fe7f9e7011c466d;p=rust.git diff --git a/src/helpers.rs b/src/helpers.rs index 9bc0279aa45..7f99aa19970 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -1,35 +1,26 @@ -use std::ffi::{OsStr, OsString}; -use std::path::{Path, PathBuf}; -use std::{iter, mem}; -use std::convert::TryFrom; -use std::borrow::Cow; - -#[cfg(unix)] -use std::os::unix::ffi::{OsStrExt, OsStringExt}; -#[cfg(windows)] -use std::os::windows::ffi::{OsStrExt, OsStringExt}; - -use rustc::mir; -use rustc::ty::{ - self, - layout::{self, LayoutOf, Size, TyLayout}, - List, TyCtxt, -}; +use std::convert::{TryFrom, TryInto}; +use std::mem; +use std::num::NonZeroUsize; +use std::time::Duration; + +use log::trace; + use rustc_hir::def_id::{DefId, CRATE_DEF_INDEX}; -use rustc_span::source_map::DUMMY_SP; +use rustc_middle::mir; +use rustc_middle::ty::{self, layout::TyAndLayout, List, TyCtxt}; +use rustc_target::abi::{Align, FieldsShape, LayoutOf, Size, Variants}; +use rustc_target::spec::abi::Abi; use rand::RngCore; use crate::*; -impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {} +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {} /// Gets an instance for a path. fn try_resolve_did<'mir, 'tcx>(tcx: TyCtxt<'tcx>, path: &[&str]) -> Option { - tcx.crates() - .iter() - .find(|&&krate| tcx.original_crate_name(krate).as_str() == path[0]) - .and_then(|krate| { + tcx.crates().iter().find(|&&krate| tcx.crate_name(krate).as_str() == path[0]).and_then( + |krate| { let krate = DefId { krate: *krate, index: CRATE_DEF_INDEX }; let mut items = tcx.item_children(krate); let mut path_it = path.iter().skip(1).peekable(); @@ -47,7 +38,8 @@ fn try_resolve_did<'mir, 'tcx>(tcx: TyCtxt<'tcx>, path: &[&str]) -> Option: crate::MiriEvalContextExt<'mir, 'tcx> { @@ -60,23 +52,18 @@ fn resolve_path(&self, path: &[&str]) -> ty::Instance<'tcx> { /// Evaluates the scalar at the specified path. Returns Some(val) /// if the path could be resolved, and None otherwise - fn eval_path_scalar( - &mut self, - path: &[&str], - ) -> InterpResult<'tcx, ScalarMaybeUndef> { + fn eval_path_scalar(&mut self, path: &[&str]) -> InterpResult<'tcx, ScalarMaybeUninit> { let this = self.eval_context_mut(); let instance = this.resolve_path(path); let cid = GlobalId { instance, promoted: None }; - let const_val = this.const_eval_raw(cid)?; - let const_val = this.read_scalar(const_val.into())?; + let const_val = this.eval_to_allocation(cid)?; + let const_val = this.read_scalar(&const_val.into())?; return Ok(const_val); } /// Helper function to get a `libc` constant as a `Scalar`. fn eval_libc(&mut self, name: &str) -> InterpResult<'tcx, Scalar> { - self.eval_context_mut() - .eval_path_scalar(&["libc", name])? - .not_undef() + self.eval_context_mut().eval_path_scalar(&["libc", name])?.check_init() } /// Helper function to get a `libc` constant as an `i32`. @@ -85,22 +72,44 @@ fn eval_libc_i32(&mut self, name: &str) -> InterpResult<'tcx, i32> { self.eval_libc(name)?.to_i32() } - /// Helper function to get the `TyLayout` of a `libc` type - fn libc_ty_layout(&mut self, name: &str) -> InterpResult<'tcx, TyLayout<'tcx>> { + /// Helper function to get a `windows` constant as a `Scalar`. + fn eval_windows(&mut self, module: &str, name: &str) -> InterpResult<'tcx, Scalar> { + self.eval_context_mut() + .eval_path_scalar(&["std", "sys", "windows", module, name])? + .check_init() + } + + /// Helper function to get a `windows` constant as an `u64`. + fn eval_windows_u64(&mut self, module: &str, name: &str) -> InterpResult<'tcx, u64> { + // TODO: Cache the result. + self.eval_windows(module, name)?.to_u64() + } + + /// Helper function to get the `TyAndLayout` of a `libc` type + fn libc_ty_layout(&mut self, name: &str) -> InterpResult<'tcx, TyAndLayout<'tcx>> { + let this = self.eval_context_mut(); + let ty = this.resolve_path(&["libc", name]).ty(*this.tcx, ty::ParamEnv::reveal_all()); + this.layout_of(ty) + } + + /// Helper function to get the `TyAndLayout` of a `windows` type + fn windows_ty_layout(&mut self, name: &str) -> InterpResult<'tcx, TyAndLayout<'tcx>> { let this = self.eval_context_mut(); - let ty = this.resolve_path(&["libc", name]).monomorphic_ty(*this.tcx); + let ty = this + .resolve_path(&["std", "sys", "windows", "c", name]) + .ty(*this.tcx, ty::ParamEnv::reveal_all()); this.layout_of(ty) } /// Write a 0 of the appropriate size to `dest`. - fn write_null(&mut self, dest: PlaceTy<'tcx, Tag>) -> InterpResult<'tcx> { + fn write_null(&mut self, dest: &PlaceTy<'tcx, Tag>) -> InterpResult<'tcx> { self.eval_context_mut().write_scalar(Scalar::from_int(0, dest.layout.size), dest) } /// Test if this immediate equals 0. fn is_null(&self, val: Scalar) -> InterpResult<'tcx, bool> { let this = self.eval_context_ref(); - let null = Scalar::ptr_null(this); + let null = Scalar::null_ptr(this); this.ptr_eq(val, null) } @@ -114,7 +123,7 @@ fn test_null(&self, val: Scalar) -> InterpResult<'tcx, Option>> fn local_place(&mut self, local: mir::Local) -> InterpResult<'tcx, PlaceTy<'tcx, Tag>> { let this = self.eval_context_mut(); let place = mir::Place { local: local, projection: List::empty() }; - this.eval_place(&place) + this.eval_place(place) } /// Generate some random bytes, and write them to `dest`. @@ -148,31 +157,39 @@ fn gen_random(&mut self, ptr: Scalar, len: u64) -> InterpResult<'tcx> { fn call_function( &mut self, f: ty::Instance<'tcx>, + caller_abi: Abi, args: &[Immediate], - dest: Option>, + dest: Option<&PlaceTy<'tcx, Tag>>, stack_pop: StackPopCleanup, ) -> InterpResult<'tcx> { let this = self.eval_context_mut(); + let param_env = ty::ParamEnv::reveal_all(); // in Miri this is always the param_env we use... and this.param_env is private. + let callee_abi = f.ty(*this.tcx, param_env).fn_sig(*this.tcx).abi(); + if this.machine.enforce_abi && callee_abi != caller_abi { + throw_ub_format!( + "calling a function with ABI {} using caller ABI {}", + callee_abi.name(), + caller_abi.name() + ) + } // Push frame. let mir = &*this.load_mir(f.def, None)?; - let span = this - .stack() - .last() - .and_then(Frame::current_source_info) - .map(|si| si.span) - .unwrap_or(DUMMY_SP); - this.push_stack_frame(f, span, mir, dest, stack_pop)?; + this.push_stack_frame(f, mir, dest, stack_pop)?; // Initialize arguments. let mut callee_args = this.frame().body.args_iter(); for arg in args { let callee_arg = this.local_place( - callee_args.next().expect("callee has fewer arguments than expected"), + callee_args + .next() + .ok_or_else(|| err_ub_format!("callee has fewer arguments than expected"))?, )?; - this.write_immediate(*arg, callee_arg)?; + this.write_immediate(*arg, &callee_arg)?; + } + if callee_args.next().is_some() { + throw_ub_format!("callee has more arguments than expected"); } - callee_args.next().expect_none("callee has more arguments than expected"); Ok(()) } @@ -181,7 +198,7 @@ fn call_function( /// will be true if this is frozen, false if this is in an `UnsafeCell`. fn visit_freeze_sensitive( &self, - place: MPlaceTy<'tcx, Tag>, + place: &MPlaceTy<'tcx, Tag>, size: Size, mut action: impl FnMut(Pointer, Size, bool) -> InterpResult<'tcx>, ) -> InterpResult<'tcx> { @@ -230,7 +247,7 @@ fn visit_freeze_sensitive( 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)? + .size_and_align_of_mplace(&place)? .map(|(size, _)| size) // for extern types, just cover what we can .unwrap_or_else(|| place.layout.size); @@ -254,16 +271,16 @@ fn visit_freeze_sensitive( /// whether we are inside an `UnsafeCell` or not. struct UnsafeCellVisitor<'ecx, 'mir, 'tcx, F> where - F: FnMut(MPlaceTy<'tcx, Tag>) -> InterpResult<'tcx>, + F: FnMut(&MPlaceTy<'tcx, Tag>) -> InterpResult<'tcx>, { ecx: &'ecx MiriEvalContext<'mir, 'tcx>, unsafe_cell_action: F, } - impl<'ecx, 'mir, 'tcx, F> ValueVisitor<'mir, 'tcx, Evaluator<'tcx>> + impl<'ecx, 'mir, 'tcx: 'mir, F> ValueVisitor<'mir, 'tcx, Evaluator<'mir, 'tcx>> for UnsafeCellVisitor<'ecx, 'mir, 'tcx, F> where - F: FnMut(MPlaceTy<'tcx, Tag>) -> InterpResult<'tcx>, + F: FnMut(&MPlaceTy<'tcx, Tag>) -> InterpResult<'tcx>, { type V = MPlaceTy<'tcx, Tag>; @@ -273,9 +290,9 @@ fn ecx(&self) -> &MiriEvalContext<'mir, 'tcx> { } // Hook to detect `UnsafeCell`. - fn visit_value(&mut self, v: MPlaceTy<'tcx, Tag>) -> InterpResult<'tcx> { + fn visit_value(&mut self, v: &MPlaceTy<'tcx, Tag>) -> InterpResult<'tcx> { trace!("UnsafeCellVisitor: {:?} {:?}", *v, v.layout.ty); - let is_unsafe_cell = match v.layout.ty.kind { + let is_unsafe_cell = match v.layout.ty.kind() { ty::Adt(adt, _) => Some(adt.did) == self.ecx.tcx.lang_items().unsafe_cell_type(), _ => false, @@ -286,12 +303,15 @@ fn visit_value(&mut self, v: MPlaceTy<'tcx, Tag>) -> InterpResult<'tcx> { } else if self.ecx.type_is_freeze(v.layout.ty) { // This is `Freeze`, there cannot be an `UnsafeCell` 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 { // 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 { - layout::Variants::Multiple { .. } => { + Variants::Multiple { .. } => { // A multi-variant enum, or generator, or so. // Treat this like a union: without reading from memory, // we cannot determine the variant we are in. Reading from @@ -301,7 +321,7 @@ fn visit_value(&mut self, v: MPlaceTy<'tcx, Tag>) -> InterpResult<'tcx> { // `UnsafeCell` action. (self.unsafe_cell_action)(v) } - layout::Variants::Single { .. } => { + Variants::Single { .. } => { // Proceed further, try to find where exactly that `UnsafeCell` // is hiding. self.walk_value(v) @@ -313,47 +333,44 @@ fn visit_value(&mut self, v: MPlaceTy<'tcx, Tag>) -> InterpResult<'tcx> { // Make sure we visit aggregrates in increasing offset order. fn visit_aggregate( &mut self, - place: MPlaceTy<'tcx, Tag>, + place: &MPlaceTy<'tcx, Tag>, fields: impl Iterator>>, ) -> InterpResult<'tcx> { match place.layout.fields { - layout::FieldPlacement::Array { .. } => { + FieldsShape::Array { .. } => { // For the array layout, we know the iterator will yield sorted elements so // we can avoid the allocation. self.walk_aggregate(place, fields) } - layout::FieldPlacement::Arbitrary { .. } => { + FieldsShape::Arbitrary { .. } => { // Gather the subplaces and sort them before visiting. let mut places = fields.collect::>>>()?; places.sort_by_key(|place| place.ptr.assert_ptr().offset); self.walk_aggregate(place, places.into_iter().map(Ok)) } - layout::FieldPlacement::Union { .. } => { + FieldsShape::Union { .. } | FieldsShape::Primitive => { // Uh, what? - bug!("a union is not an aggregate we should ever visit") + bug!("unions/primitives are not aggregates we should ever visit") } } } - // We have to do *something* for unions. - fn visit_union(&mut self, v: MPlaceTy<'tcx, Tag>, fields: usize) -> InterpResult<'tcx> { - assert!(fields > 0); // we should never reach "pseudo-unions" with 0 fields, like primitives - - // With unions, we fall back to whatever the type says, to hopefully be consistent - // with LLVM IR. - // FIXME: are we consistent, and is this really the behavior we want? - let frozen = self.ecx.type_is_freeze(v.layout.ty); - if frozen { Ok(()) } else { (self.unsafe_cell_action)(v) } + fn visit_union( + &mut self, + _v: &MPlaceTy<'tcx, Tag>, + _fields: NonZeroUsize, + ) -> InterpResult<'tcx> { + bug!("we should have already handled unions in `visit_value`") } } } - // Writes several `ImmTy`s contiguosly into memory. This is useful when you have to pack + // Writes several `ImmTy`s contiguously into memory. This is useful when you have to pack // different values into a struct. fn write_packed_immediates( &mut self, - place: MPlaceTy<'tcx, Tag>, + place: &MPlaceTy<'tcx, Tag>, imms: &[ImmTy<'tcx, Tag>], ) -> InterpResult<'tcx> { let this = self.eval_context_mut(); @@ -363,7 +380,7 @@ fn write_packed_immediates( for &imm in imms { this.write_immediate_to_mplace( *imm, - place.offset(offset, MemPlaceMeta::None, imm.layout, &*this.tcx)?, + &place.offset(offset, MemPlaceMeta::None, imm.layout, &*this.tcx)?, )?; offset += imm.layout.size; } @@ -375,19 +392,17 @@ fn write_packed_immediates( /// case. fn check_no_isolation(&self, name: &str) -> InterpResult<'tcx> { if !self.eval_context_ref().machine.communicate { - throw_machine_stop!(TerminationInfo::UnsupportedInIsolation(format!( - "`{}` not available when isolation is enabled", - name, - ))) + isolation_error(name)?; } Ok(()) } + /// 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.target.target_os, + self.eval_context_ref().tcx.sess.target.os, target_os, "`{}` is only available on the `{}` target OS", name, @@ -395,18 +410,34 @@ fn assert_target_os(&self, target_os: &str, name: &str) { ) } + /// Get last error variable as a place, lazily allocating thread-local storage for it if + /// necessary. + fn last_error_place(&mut self) -> InterpResult<'tcx, MPlaceTy<'tcx, Tag>> { + let this = self.eval_context_mut(); + if let Some(errno_place) = this.active_thread_ref().last_error { + Ok(errno_place) + } else { + // Allocate new place, set initial value to 0. + let errno_layout = this.machine.layouts.u32; + let errno_place = this.allocate(errno_layout, MiriMemoryKind::Machine.into()); + this.write_scalar(Scalar::from_u32(0), &errno_place.into())?; + this.active_thread_mut().last_error = Some(errno_place); + Ok(errno_place) + } + } + /// Sets the last error variable. fn set_last_error(&mut self, scalar: Scalar) -> InterpResult<'tcx> { let this = self.eval_context_mut(); - let errno_place = this.machine.last_error.unwrap(); - this.write_scalar(scalar, errno_place.into()) + let errno_place = this.last_error_place()?; + this.write_scalar(scalar, &errno_place.into()) } /// Gets the last error variable. - fn get_last_error(&self) -> InterpResult<'tcx, Scalar> { - let this = self.eval_context_ref(); - let errno_place = this.machine.last_error.unwrap(); - this.read_scalar(errno_place.into())?.not_undef() + fn get_last_error(&mut self) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + let errno_place = this.last_error_place()?; + this.read_scalar(&errno_place.into())?.check_init() } /// Sets the last OS error using a `std::io::Error`. This function tries to produce the most @@ -414,8 +445,9 @@ fn get_last_error(&self) -> InterpResult<'tcx, Scalar> { fn set_last_error_from_io_error(&mut self, e: std::io::Error) -> InterpResult<'tcx> { use std::io::ErrorKind::*; let this = self.eval_context_mut(); - let target = &this.tcx.sess.target.target; - let last_error = if target.options.target_family == Some("unix".to_owned()) { + let target = &this.tcx.sess.target; + let target_os = &target.os; + let last_error = if target.families.contains(&"unix".to_owned()) { this.eval_libc(match e.kind() { ConnectionRefused => "ECONNREFUSED", ConnectionReset => "ECONNRESET", @@ -435,11 +467,22 @@ fn set_last_error_from_io_error(&mut self, e: std::io::Error) -> InterpResult<'t throw_unsup_format!("io error {} cannot be transformed into a raw os error", e) } })? + } else if target.families.contains(&"windows".to_owned()) { + // FIXME: we have to finish implementing the Windows equivalent of this. + this.eval_windows( + "c", + match e.kind() { + NotFound => "ERROR_FILE_NOT_FOUND", + _ => throw_unsup_format!( + "io error {} cannot be transformed into a raw os error", + e + ), + }, + )? } else { - // FIXME: we have to implement the Windows equivalent of this. throw_unsup_format!( "setting the last OS error from an io::Error is unsupported for {}.", - target.target_os + target_os ) }; this.set_last_error(last_error) @@ -464,241 +507,158 @@ fn try_unwrap_io_result>( } } - /// Helper function to read an OsString from a null-terminated sequence of bytes, which is what - /// the Unix APIs usually handle. - fn read_os_str_from_c_str<'a>(&'a self, scalar: Scalar) -> InterpResult<'tcx, &'a OsStr> - where - 'tcx: 'a, - 'mir: 'a, - { - #[cfg(unix)] - fn bytes_to_os_str<'tcx, 'a>(bytes: &'a [u8]) -> InterpResult<'tcx, &'a OsStr> { - Ok(OsStr::from_bytes(bytes)) - } - #[cfg(not(unix))] - fn bytes_to_os_str<'tcx, 'a>(bytes: &'a [u8]) -> InterpResult<'tcx, &'a OsStr> { - let s = std::str::from_utf8(bytes) - .map_err(|_| err_unsup_format!("{:?} is not a valid utf-8 string", bytes))?; - Ok(OsStr::new(s)) - } - + fn read_scalar_at_offset( + &self, + op: &OpTy<'tcx, Tag>, + offset: u64, + layout: TyAndLayout<'tcx>, + ) -> InterpResult<'tcx, ScalarMaybeUninit> { let this = self.eval_context_ref(); - let bytes = this.memory.read_c_str(scalar)?; - bytes_to_os_str(bytes) - } - - /// Helper function to read an OsString from a 0x0000-terminated sequence of u16, - /// which is what the Windows APIs usually handle. - fn read_os_str_from_wide_str<'a>(&'a self, scalar: Scalar) -> InterpResult<'tcx, OsString> - where - 'tcx: 'a, - 'mir: 'a, - { - #[cfg(windows)] - pub fn u16vec_to_osstring<'tcx, 'a>(u16_vec: Vec) -> InterpResult<'tcx, OsString> { - Ok(OsString::from_wide(&u16_vec[..])) - } - #[cfg(not(windows))] - pub fn u16vec_to_osstring<'tcx, 'a>(u16_vec: Vec) -> InterpResult<'tcx, OsString> { - let s = String::from_utf16(&u16_vec[..]) - .map_err(|_| err_unsup_format!("{:?} is not a valid utf-16 string", u16_vec))?; - Ok(s.into()) - } - - let u16_vec = self.eval_context_ref().memory.read_wide_str(scalar)?; - u16vec_to_osstring(u16_vec) - } - - /// Helper function to write an OsStr as a null-terminated sequence of bytes, 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 `os_string` plus a null - /// terminator. It returns `Ok((true, length))` if the writing process was successful. The - /// string length returned does not include the null terminator. - fn write_os_str_to_c_str( - &mut self, - os_str: &OsStr, - scalar: Scalar, - size: u64, - ) -> InterpResult<'tcx, (bool, u64)> { - #[cfg(unix)] - fn os_str_to_bytes<'tcx, 'a>(os_str: &'a OsStr) -> InterpResult<'tcx, &'a [u8]> { - Ok(os_str.as_bytes()) - } - #[cfg(not(unix))] - fn os_str_to_bytes<'tcx, 'a>(os_str: &'a OsStr) -> InterpResult<'tcx, &'a [u8]> { - // On non-unix platforms the best we can do to transform bytes from/to OS strings is to do the - // intermediate transformation into strings. Which invalidates non-utf8 paths that are actually - // valid. - os_str - .to_str() - .map(|s| s.as_bytes()) - .ok_or_else(|| err_unsup_format!("{:?} is not a valid utf-8 string", os_str).into()) - } - - let bytes = os_str_to_bytes(os_str)?; - // 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(bytes.len()).unwrap(); - if size <= string_length { - return Ok((false, string_length)); - } - self.eval_context_mut() - .memory - .write_bytes(scalar, bytes.iter().copied().chain(iter::once(0u8)))?; - Ok((true, string_length)) - } - - /// Helper function to write an OsStr as a 0x0000-terminated u16-sequence, 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 not include the null terminator. - fn write_os_str_to_wide_str( - &mut self, - os_str: &OsStr, - scalar: Scalar, - size: u64, - ) -> InterpResult<'tcx, (bool, u64)> { - #[cfg(windows)] - fn os_str_to_u16vec<'tcx>(os_str: &OsStr) -> InterpResult<'tcx, Vec> { - Ok(os_str.encode_wide().collect()) - } - #[cfg(not(windows))] - fn os_str_to_u16vec<'tcx>(os_str: &OsStr) -> InterpResult<'tcx, Vec> { - // On non-Windows platforms the best we can do to transform Vec from/to OS strings is to do the - // intermediate transformation into strings. Which invalidates non-utf8 paths that are actually - // valid. - os_str - .to_str() - .map(|s| s.encode_utf16().collect()) - .ok_or_else(|| err_unsup_format!("{:?} is not a valid utf-8 string", os_str).into()) - } - - let u16_vec = os_str_to_u16vec(os_str)?; - // 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(u16_vec.len()).unwrap(); - if size <= string_length { - return Ok((false, string_length)); - } - - // Store the UTF-16 string. - self.eval_context_mut() - .memory - .write_u16s(scalar, u16_vec.into_iter().chain(iter::once(0x0000)))?; - Ok((true, string_length)) + let op_place = this.deref_operand(op)?; + let offset = Size::from_bytes(offset); + // Ensure that the following read at an offset is within bounds + assert!(op_place.layout.size >= offset + layout.size); + let value_place = op_place.offset(offset, MemPlaceMeta::None, layout, this)?; + this.read_scalar(&value_place.into()) } - /// Allocate enough memory to store the given `OsStr` as a null-terminated sequence of bytes. - fn alloc_os_str_as_c_str( + fn write_scalar_at_offset( &mut self, - os_str: &OsStr, - memkind: MemoryKind, - ) -> Pointer { - let size = u64::try_from(os_str.len()).unwrap().checked_add(1).unwrap(); // Make space for `0` terminator. + op: &OpTy<'tcx, Tag>, + offset: u64, + value: impl Into>, + layout: TyAndLayout<'tcx>, + ) -> InterpResult<'tcx, ()> { let this = self.eval_context_mut(); - - let arg_type = this.tcx.mk_array(this.tcx.types.u8, size); - let arg_place = this.allocate(this.layout_of(arg_type).unwrap(), memkind); - assert!(self.write_os_str_to_c_str(os_str, arg_place.ptr, size).unwrap().0); - arg_place.ptr.assert_ptr() + let op_place = this.deref_operand(op)?; + let offset = Size::from_bytes(offset); + // Ensure that the following read at an offset is within bounds + assert!(op_place.layout.size >= offset + layout.size); + let value_place = op_place.offset(offset, MemPlaceMeta::None, layout, this)?; + this.write_scalar(value, &value_place.into()) } - /// Allocate enough memory to store the given `OsStr` as a null-terminated sequence of `u16`. - fn alloc_os_str_as_wide_str( + /// 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, - os_str: &OsStr, - memkind: MemoryKind, - ) -> Pointer { - let size = u64::try_from(os_str.len()).unwrap().checked_add(1).unwrap(); // Make space for `0x0000` terminator. + timespec_ptr_op: &OpTy<'tcx, Tag>, + ) -> InterpResult<'tcx, Option> { let this = self.eval_context_mut(); - - let arg_type = this.tcx.mk_array(this.tcx.types.u16, size); - let arg_place = this.allocate(this.layout_of(arg_type).unwrap(), memkind); - assert!(self.write_os_str_to_wide_str(os_str, arg_place.ptr, size).unwrap().0); - arg_place.ptr.assert_ptr() + let tp = this.deref_operand(timespec_ptr_op)?; + let seconds_place = this.mplace_field(&tp, 0)?; + let seconds_scalar = this.read_scalar(&seconds_place.into())?; + let seconds = seconds_scalar.to_machine_isize(this)?; + let nanoseconds_place = this.mplace_field(&tp, 1)?; + let nanoseconds_scalar = this.read_scalar(&nanoseconds_place.into())?; + let nanoseconds = nanoseconds_scalar.to_machine_isize(this)?; + + 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 a null-terminated sequence of bytes, and perform path separator conversion if needed. - fn read_path_from_c_str<'a>(&'a self, scalar: Scalar) -> InterpResult<'tcx, Cow<'a, Path>> + fn read_c_str<'a>(&'a self, sptr: Scalar) -> InterpResult<'tcx, &'a [u8]> where 'tcx: 'a, 'mir: 'a, { let this = self.eval_context_ref(); - let os_str = this.read_os_str_from_c_str(scalar)?; + let size1 = Size::from_bytes(1); + let ptr = this.force_ptr(sptr)?; // We need to read at least 1 byte, so we can eagerly get a ptr. + + // 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.memory.get(ptr.offset(len, this)?.into(), size1, Align::ONE)?.unwrap(); // not a ZST, so we will get a result + let byte = alloc.read_scalar(alloc_range(Size::ZERO, size1))?.to_u8()?; + if byte == 0 { + break; + } else { + len = len + size1; + } + } - #[cfg(windows)] - return Ok(if this.tcx.sess.target.target.target_os == "windows" { - // Windows-on-Windows, all fine. - Cow::Borrowed(Path::new(os_str)) - } else { - // Unix target, Windows host. Need to convert target '/' to host '\'. - let converted = os_str - .encode_wide() - .map(|wchar| if wchar == '/' as u16 { '\\' as u16 } else { wchar }) - .collect::>(); - Cow::Owned(PathBuf::from(OsString::from_wide(&converted))) - }); - #[cfg(unix)] - return Ok(if this.tcx.sess.target.target.target_os == "windows" { - // Windows target, Unix host. Need to convert target '\' to host '/'. - let converted = os_str - .as_bytes() - .iter() - .map(|&wchar| if wchar == '/' as u8 { '\\' as u8 } else { wchar }) - .collect::>(); - Cow::Owned(PathBuf::from(OsString::from_vec(converted))) - } else { - // Unix-on-Unix, all is fine. - Cow::Borrowed(Path::new(os_str)) - }); + // Step 2: get the bytes. + this.memory.read_bytes(ptr.into(), len) } - /// Write a Path to the machine memory, adjusting path separators if needed. - fn write_path_to_c_str( - &mut self, - path: &Path, - scalar: Scalar, - size: u64, - ) -> InterpResult<'tcx, (bool, u64)> { - let this = self.eval_context_mut(); + fn read_wide_str(&self, sptr: Scalar) -> InterpResult<'tcx, Vec> { + let this = self.eval_context_ref(); + let size2 = Size::from_bytes(2); + let align2 = Align::from_bytes(2).unwrap(); + + let mut ptr = this.force_ptr(sptr)?; // We need to read at least 1 wchar, so we can eagerly get a ptr. + 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.memory.get(ptr.into(), size2, align2)?.unwrap(); // not a ZST, so we will get a result + let wchar = alloc.read_scalar(alloc_range(Size::ZERO, size2))?.to_u16()?; + if wchar == 0 { + break; + } else { + wchars.push(wchar); + ptr = ptr.offset(size2, this)?; + } + } - #[cfg(windows)] - let os_str = if this.tcx.sess.target.target.target_os == "windows" { - // Windows-on-Windows, all fine. - Cow::Borrowed(path.as_os_str()) - } else { - // Unix target, Windows host. Need to convert host '\\' to target '/'. - let converted = path - .as_os_str() - .encode_wide() - .map(|wchar| if wchar == '\\' as u16 { '/' as u16 } else { wchar }) - .collect::>(); - Cow::Owned(OsString::from_wide(&converted)) - }; - #[cfg(unix)] - let os_str = if this.tcx.sess.target.target.target_os == "windows" { - // Windows target, Unix host. Need to convert host '/' to target '\'. - let converted = path - .as_os_str() - .as_bytes() - .iter() - .map(|&wchar| if wchar == '/' as u8 { '\\' as u8 } else { wchar }) - .collect::>(); - Cow::Owned(OsString::from_vec(converted)) - } else { - // Unix-on-Unix, all is fine. - Cow::Borrowed(path.as_os_str()) - }; + Ok(wchars) + } - this.write_os_str_to_c_str(&os_str, scalar, size) + /// Check that the ABI is what we expect. + fn check_abi<'a>(&self, abi: Abi, exp_abi: Abi) -> InterpResult<'a, ()> { + if self.eval_context_ref().machine.enforce_abi && abi != exp_abi { + throw_ub_format!( + "calling a function with ABI {} using caller ABI {}", + exp_abi.name(), + abi.name() + ) + } + Ok(()) + } + + fn in_std(&self) -> bool { + let this = self.eval_context_ref(); + this.tcx.def_path(this.frame().instance.def_id()).krate + == this.tcx.def_path(this.tcx.lang_items().start_fn().unwrap()).krate } } +/// Check that the number of args is what we expect. +pub fn check_arg_count<'a, 'tcx, const N: usize>( + args: &'a [OpTy<'tcx, Tag>], +) -> InterpResult<'tcx, &'a [OpTy<'tcx, Tag>; N]> +where + &'a [OpTy<'tcx, Tag>; N]: TryFrom<&'a [OpTy<'tcx, Tag>]>, +{ + if let Ok(ops) = args.try_into() { + return Ok(ops); + } + throw_ub_format!("incorrect number of arguments: got {}, expected {}", args.len(), N) +} + +pub fn isolation_error(name: &str) -> InterpResult<'static> { + throw_machine_stop!(TerminationInfo::UnsupportedInIsolation(format!( + "{} not available when isolation is enabled", + name, + ))) +} + pub fn immty_from_int_checked<'tcx>( int: impl Into, - layout: TyLayout<'tcx>, + layout: TyAndLayout<'tcx>, ) -> InterpResult<'tcx, ImmTy<'tcx, Tag>> { let int = int.into(); Ok(ImmTy::try_from_int(int, layout).ok_or_else(|| { @@ -708,7 +668,7 @@ pub fn immty_from_int_checked<'tcx>( pub fn immty_from_uint_checked<'tcx>( int: impl Into, - layout: TyLayout<'tcx>, + layout: TyAndLayout<'tcx>, ) -> InterpResult<'tcx, ImmTy<'tcx, Tag>> { let int = int.into(); Ok(ImmTy::try_from_uint(int, layout).ok_or_else(|| {