diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index cbe70cbffee..8b286871774 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -31,7 +31,7 @@ use rustc_target::spec::abi::Abi; use crate::{ concurrency::{data_race, weak_memory}, - shims::unix::FdTable, + shims::unix, *, }; @@ -439,8 +439,7 @@ pub struct MiriMachine<'mir, 'tcx> { /// Ptr-int-cast module global data. pub alloc_addresses: alloc_addresses::GlobalState, - /// Environment variables set by `setenv`. - /// Miri does not expose env vars from the host to the emulated program. + /// Environment variables. pub(crate) env_vars: EnvVars<'tcx>, /// Return place of the main function. @@ -465,9 +464,9 @@ pub struct MiriMachine<'mir, 'tcx> { pub(crate) validate: bool, /// The table of file descriptors. - pub(crate) fds: shims::unix::FdTable, + pub(crate) fds: unix::FdTable, /// The table of directory descriptors. - pub(crate) dirs: shims::unix::DirTable, + pub(crate) dirs: unix::DirTable, /// This machine's monotone clock. pub(crate) clock: Clock, @@ -642,7 +641,7 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> { tls: TlsData::default(), isolated_op: config.isolated_op, validate: config.validate, - fds: FdTable::new(config.mute_stdout_stderr), + fds: unix::FdTable::new(config.mute_stdout_stderr), dirs: Default::default(), layouts, threads: ThreadManager::default(), diff --git a/src/tools/miri/src/shims/env.rs b/src/tools/miri/src/shims/env.rs index 298fefdb0f3..fc0160fdf21 100644 --- a/src/tools/miri/src/shims/env.rs +++ b/src/tools/miri/src/shims/env.rs @@ -1,33 +1,24 @@ -use std::env; -use std::ffi::{OsStr, OsString}; -use std::io::ErrorKind; -use std::mem; +use std::ffi::OsString; use rustc_data_structures::fx::FxHashMap; -use rustc_middle::ty::layout::LayoutOf; -use rustc_middle::ty::Ty; -use rustc_target::abi::Size; use crate::*; -use helpers::windows_check_buffer_size; +use shims::{unix::UnixEnvVars, windows::WindowsEnvVars}; #[derive(Default)] -pub struct EnvVars<'tcx> { - /// Stores pointers to the environment variables. These variables must be stored as - /// null-terminated target strings (c_str or wide_str) with the `"{name}={value}"` format. - map: FxHashMap>>, - - /// Place where the `environ` static is stored. Lazily initialized, but then never changes. - pub(crate) environ: Option>, +pub enum EnvVars<'tcx> { + #[default] + Uninit, + Unix(UnixEnvVars<'tcx>), + Windows(WindowsEnvVars), } impl VisitProvenance for EnvVars<'_> { fn visit_provenance(&self, visit: &mut VisitWith<'_>) { - let EnvVars { map, environ } = self; - - environ.visit_provenance(visit); - for ptr in map.values() { - ptr.visit_provenance(visit); + match self { + EnvVars::Uninit => {} + EnvVars::Unix(env) => env.visit_provenance(visit), + EnvVars::Windows(env) => env.visit_provenance(visit), } } } @@ -39,517 +30,73 @@ impl<'tcx> EnvVars<'tcx> { ) -> InterpResult<'tcx> { // Initialize the `env_vars` map. // Skip the loop entirely if we don't want to forward anything. + let mut env_vars = FxHashMap::default(); if ecx.machine.communicate() || !config.forwarded_env_vars.is_empty() { for (name, value) in &config.env { let forward = ecx.machine.communicate() || config.forwarded_env_vars.iter().any(|v| **v == *name); if forward { - add_env_var(ecx, name, value)?; + env_vars.insert(OsString::from(name), OsString::from(value)); } } } for (name, value) in &config.set_env_vars { - add_env_var(ecx, OsStr::new(name), OsStr::new(value))?; + env_vars.insert(OsString::from(name), OsString::from(value)); } - // Initialize the `environ` pointer when needed. - if ecx.target_os_is_unix() { - // This is memory backing an extern static, hence `ExternStatic`, not `Env`. - let layout = ecx.machine.layouts.mut_raw_ptr; - let place = ecx.allocate(layout, MiriMemoryKind::ExternStatic.into())?; - ecx.write_null(&place)?; - ecx.machine.env_vars.environ = Some(place); - ecx.update_environ()?; - } + let env_vars = if ecx.target_os_is_unix() { + EnvVars::Unix(UnixEnvVars::new(ecx, env_vars)?) + } else if ecx.tcx.sess.target.os == "windows" { + EnvVars::Windows(WindowsEnvVars::new(ecx, env_vars)?) + } else { + // Used e.g. for wasi + EnvVars::Uninit + }; + ecx.machine.env_vars = env_vars; + Ok(()) } pub(crate) fn cleanup<'mir>( ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>, ) -> InterpResult<'tcx> { - // Deallocate individual env vars. - let env_vars = mem::take(&mut ecx.machine.env_vars.map); - for (_name, ptr) in env_vars { - ecx.deallocate_ptr(ptr, None, MiriMemoryKind::Runtime.into())?; + let this = ecx.eval_context_mut(); + match this.machine.env_vars { + EnvVars::Unix(_) => UnixEnvVars::cleanup(this), + EnvVars::Windows(_) => Ok(()), // no cleanup needed + EnvVars::Uninit => Ok(()), } - // Deallocate environ var list. - if ecx.target_os_is_unix() { - let environ = ecx.machine.env_vars.environ.as_ref().unwrap(); - let old_vars_ptr = ecx.read_pointer(environ)?; - ecx.deallocate_ptr(old_vars_ptr, None, MiriMemoryKind::Runtime.into())?; - } - Ok(()) } -} -fn add_env_var<'mir, 'tcx>( - ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>, - name: &OsStr, - value: &OsStr, -) -> InterpResult<'tcx, ()> { - let var_ptr = match ecx.tcx.sess.target.os.as_ref() { - _ if ecx.target_os_is_unix() => alloc_env_var_as_c_str(name, value, ecx)?, - "windows" => alloc_env_var_as_wide_str(name, value, ecx)?, - unsupported => - throw_unsup_format!( - "environment support for target OS `{}` not yet available", - unsupported - ), - }; - ecx.machine.env_vars.map.insert(name.to_os_string(), var_ptr); - Ok(()) -} + pub(crate) fn unix(&self) -> &UnixEnvVars<'tcx> { + match self { + EnvVars::Unix(env) => env, + _ => unreachable!(), + } + } -fn alloc_env_var_as_c_str<'mir, 'tcx>( - name: &OsStr, - value: &OsStr, - ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>, -) -> InterpResult<'tcx, Pointer>> { - let mut name_osstring = name.to_os_string(); - name_osstring.push("="); - name_osstring.push(value); - ecx.alloc_os_str_as_c_str(name_osstring.as_os_str(), MiriMemoryKind::Runtime.into()) -} + pub(crate) fn unix_mut(&mut self) -> &mut UnixEnvVars<'tcx> { + match self { + EnvVars::Unix(env) => env, + _ => unreachable!(), + } + } -fn alloc_env_var_as_wide_str<'mir, 'tcx>( - name: &OsStr, - value: &OsStr, - ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>, -) -> InterpResult<'tcx, Pointer>> { - let mut name_osstring = name.to_os_string(); - name_osstring.push("="); - name_osstring.push(value); - ecx.alloc_os_str_as_wide_str(name_osstring.as_os_str(), MiriMemoryKind::Runtime.into()) + pub(crate) fn windows(&self) -> &WindowsEnvVars { + match self { + EnvVars::Windows(env) => env, + _ => unreachable!(), + } + } + + pub(crate) fn windows_mut(&mut self) -> &mut WindowsEnvVars { + match self { + EnvVars::Windows(env) => env, + _ => unreachable!(), + } + } } impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} -pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { - fn getenv( - &mut self, - name_op: &OpTy<'tcx, Provenance>, - ) -> InterpResult<'tcx, Pointer>> { - let this = self.eval_context_mut(); - this.assert_target_os_is_unix("getenv"); - - let name_ptr = this.read_pointer(name_op)?; - let name = this.read_os_str_from_c_str(name_ptr)?; - this.read_environ()?; - Ok(match this.machine.env_vars.map.get(name) { - Some(var_ptr) => { - // The offset is used to strip the "{name}=" part of the string. - var_ptr.offset( - Size::from_bytes(u64::try_from(name.len()).unwrap().checked_add(1).unwrap()), - this, - )? - } - None => Pointer::null(), - }) - } - - #[allow(non_snake_case)] - fn GetEnvironmentVariableW( - &mut self, - name_op: &OpTy<'tcx, Provenance>, // LPCWSTR - buf_op: &OpTy<'tcx, Provenance>, // LPWSTR - size_op: &OpTy<'tcx, Provenance>, // DWORD - ) -> InterpResult<'tcx, Scalar> { - // ^ Returns DWORD (u32 on Windows) - - let this = self.eval_context_mut(); - this.assert_target_os("windows", "GetEnvironmentVariableW"); - - let name_ptr = this.read_pointer(name_op)?; - let buf_ptr = this.read_pointer(buf_op)?; - let buf_size = this.read_scalar(size_op)?.to_u32()?; // in characters - - let name = this.read_os_str_from_wide_str(name_ptr)?; - Ok(match this.machine.env_vars.map.get(&name) { - Some(&var_ptr) => { - // The offset is used to strip the "{name}=" part of the string. - #[rustfmt::skip] - let name_offset_bytes = u64::try_from(name.len()).unwrap() - .checked_add(1).unwrap() - .checked_mul(2).unwrap(); - let var_ptr = var_ptr.offset(Size::from_bytes(name_offset_bytes), this)?; - let var = this.read_os_str_from_wide_str(var_ptr)?; - - Scalar::from_u32(windows_check_buffer_size(this.write_os_str_to_wide_str( - &var, - buf_ptr, - buf_size.into(), - )?)) - // This can in fact return 0. It is up to the caller to set last_error to 0 - // beforehand and check it afterwards to exclude that case. - } - None => { - let envvar_not_found = this.eval_windows("c", "ERROR_ENVVAR_NOT_FOUND"); - this.set_last_error(envvar_not_found)?; - Scalar::from_u32(0) // return zero upon failure - } - }) - } - - #[allow(non_snake_case)] - fn GetEnvironmentStringsW(&mut self) -> InterpResult<'tcx, Pointer>> { - let this = self.eval_context_mut(); - this.assert_target_os("windows", "GetEnvironmentStringsW"); - - // Info on layout of environment blocks in Windows: - // https://docs.microsoft.com/en-us/windows/win32/procthread/environment-variables - let mut env_vars = std::ffi::OsString::new(); - for &item in this.machine.env_vars.map.values() { - let env_var = this.read_os_str_from_wide_str(item)?; - env_vars.push(env_var); - env_vars.push("\0"); - } - // Allocate environment block & Store environment variables to environment block. - // Final null terminator(block terminator) is added by `alloc_os_str_to_wide_str`. - let envblock_ptr = - this.alloc_os_str_as_wide_str(&env_vars, MiriMemoryKind::Runtime.into())?; - // If the function succeeds, the return value is a pointer to the environment block of the current process. - Ok(envblock_ptr) - } - - #[allow(non_snake_case)] - fn FreeEnvironmentStringsW( - &mut self, - env_block_op: &OpTy<'tcx, Provenance>, - ) -> InterpResult<'tcx, Scalar> { - let this = self.eval_context_mut(); - this.assert_target_os("windows", "FreeEnvironmentStringsW"); - - let env_block_ptr = this.read_pointer(env_block_op)?; - let result = this.deallocate_ptr(env_block_ptr, None, MiriMemoryKind::Runtime.into()); - // If the function succeeds, the return value is nonzero. - Ok(Scalar::from_i32(i32::from(result.is_ok()))) - } - - fn setenv( - &mut self, - name_op: &OpTy<'tcx, Provenance>, - value_op: &OpTy<'tcx, Provenance>, - ) -> InterpResult<'tcx, i32> { - let this = self.eval_context_mut(); - this.assert_target_os_is_unix("setenv"); - - let name_ptr = this.read_pointer(name_op)?; - let value_ptr = this.read_pointer(value_op)?; - - let mut new = None; - if !this.ptr_is_null(name_ptr)? { - let name = this.read_os_str_from_c_str(name_ptr)?; - if !name.is_empty() && !name.to_string_lossy().contains('=') { - let value = this.read_os_str_from_c_str(value_ptr)?; - new = Some((name.to_owned(), value.to_owned())); - } - } - if let Some((name, value)) = new { - let var_ptr = alloc_env_var_as_c_str(&name, &value, this)?; - if let Some(var) = this.machine.env_vars.map.insert(name, var_ptr) { - this.deallocate_ptr(var, None, MiriMemoryKind::Runtime.into())?; - } - this.update_environ()?; - Ok(0) // return zero on success - } else { - // name argument is a null pointer, points to an empty string, or points to a string containing an '=' character. - let einval = this.eval_libc("EINVAL"); - this.set_last_error(einval)?; - Ok(-1) - } - } - - #[allow(non_snake_case)] - fn SetEnvironmentVariableW( - &mut self, - name_op: &OpTy<'tcx, Provenance>, // LPCWSTR - value_op: &OpTy<'tcx, Provenance>, // LPCWSTR - ) -> InterpResult<'tcx, Scalar> { - let this = self.eval_context_mut(); - this.assert_target_os("windows", "SetEnvironmentVariableW"); - - let name_ptr = this.read_pointer(name_op)?; - let value_ptr = this.read_pointer(value_op)?; - - if this.ptr_is_null(name_ptr)? { - // ERROR CODE is not clearly explained in docs.. For now, throw UB instead. - throw_ub_format!("pointer to environment variable name is NULL"); - } - - let name = this.read_os_str_from_wide_str(name_ptr)?; - if name.is_empty() { - throw_unsup_format!("environment variable name is an empty string"); - } else if name.to_string_lossy().contains('=') { - throw_unsup_format!("environment variable name contains '='"); - } else if this.ptr_is_null(value_ptr)? { - // Delete environment variable `{name}` - if let Some(var) = this.machine.env_vars.map.remove(&name) { - this.deallocate_ptr(var, None, MiriMemoryKind::Runtime.into())?; - } - Ok(this.eval_windows("c", "TRUE")) - } else { - let value = this.read_os_str_from_wide_str(value_ptr)?; - let var_ptr = alloc_env_var_as_wide_str(&name, &value, this)?; - if let Some(var) = this.machine.env_vars.map.insert(name, var_ptr) { - this.deallocate_ptr(var, None, MiriMemoryKind::Runtime.into())?; - } - Ok(this.eval_windows("c", "TRUE")) - } - } - - fn unsetenv(&mut self, name_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> { - let this = self.eval_context_mut(); - this.assert_target_os_is_unix("unsetenv"); - - let name_ptr = this.read_pointer(name_op)?; - let mut success = None; - if !this.ptr_is_null(name_ptr)? { - let name = this.read_os_str_from_c_str(name_ptr)?.to_owned(); - if !name.is_empty() && !name.to_string_lossy().contains('=') { - success = Some(this.machine.env_vars.map.remove(&name)); - } - } - if let Some(old) = success { - if let Some(var) = old { - this.deallocate_ptr(var, None, MiriMemoryKind::Runtime.into())?; - } - this.update_environ()?; - Ok(0) - } else { - // name argument is a null pointer, points to an empty string, or points to a string containing an '=' character. - let einval = this.eval_libc("EINVAL"); - this.set_last_error(einval)?; - Ok(-1) - } - } - - fn getcwd( - &mut self, - buf_op: &OpTy<'tcx, Provenance>, - size_op: &OpTy<'tcx, Provenance>, - ) -> InterpResult<'tcx, Pointer>> { - let this = self.eval_context_mut(); - this.assert_target_os_is_unix("getcwd"); - - let buf = this.read_pointer(buf_op)?; - let size = this.read_target_usize(size_op)?; - - if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { - this.reject_in_isolation("`getcwd`", reject_with)?; - this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?; - return Ok(Pointer::null()); - } - - // If we cannot get the current directory, we return null - match env::current_dir() { - Ok(cwd) => { - if this.write_path_to_c_str(&cwd, buf, size)?.0 { - return Ok(buf); - } - let erange = this.eval_libc("ERANGE"); - this.set_last_error(erange)?; - } - Err(e) => this.set_last_error_from_io_error(e.kind())?, - } - - Ok(Pointer::null()) - } - - #[allow(non_snake_case)] - fn GetCurrentDirectoryW( - &mut self, - size_op: &OpTy<'tcx, Provenance>, // DWORD - buf_op: &OpTy<'tcx, Provenance>, // LPTSTR - ) -> InterpResult<'tcx, Scalar> { - let this = self.eval_context_mut(); - this.assert_target_os("windows", "GetCurrentDirectoryW"); - - let size = u64::from(this.read_scalar(size_op)?.to_u32()?); - let buf = this.read_pointer(buf_op)?; - - if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { - this.reject_in_isolation("`GetCurrentDirectoryW`", reject_with)?; - this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?; - return Ok(Scalar::from_u32(0)); - } - - // If we cannot get the current directory, we return 0 - match env::current_dir() { - Ok(cwd) => { - // This can in fact return 0. It is up to the caller to set last_error to 0 - // beforehand and check it afterwards to exclude that case. - return Ok(Scalar::from_u32(windows_check_buffer_size( - this.write_path_to_wide_str(&cwd, buf, size)?, - ))); - } - Err(e) => this.set_last_error_from_io_error(e.kind())?, - } - Ok(Scalar::from_u32(0)) - } - - fn chdir(&mut self, path_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> { - let this = self.eval_context_mut(); - this.assert_target_os_is_unix("chdir"); - - let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?; - - if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { - this.reject_in_isolation("`chdir`", reject_with)?; - this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?; - - return Ok(-1); - } - - match env::set_current_dir(path) { - Ok(()) => Ok(0), - Err(e) => { - this.set_last_error_from_io_error(e.kind())?; - Ok(-1) - } - } - } - - #[allow(non_snake_case)] - fn SetCurrentDirectoryW( - &mut self, - path_op: &OpTy<'tcx, Provenance>, // LPCTSTR - ) -> InterpResult<'tcx, Scalar> { - // ^ Returns BOOL (i32 on Windows) - - let this = self.eval_context_mut(); - this.assert_target_os("windows", "SetCurrentDirectoryW"); - - let path = this.read_path_from_wide_str(this.read_pointer(path_op)?)?; - - if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { - this.reject_in_isolation("`SetCurrentDirectoryW`", reject_with)?; - this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?; - - return Ok(this.eval_windows("c", "FALSE")); - } - - match env::set_current_dir(path) { - Ok(()) => Ok(this.eval_windows("c", "TRUE")), - Err(e) => { - this.set_last_error_from_io_error(e.kind())?; - Ok(this.eval_windows("c", "FALSE")) - } - } - } - - /// Updates the `environ` static. - /// The first time it gets called, also initializes `extra.environ`. - fn update_environ(&mut self) -> InterpResult<'tcx> { - let this = self.eval_context_mut(); - // Deallocate the old environ list, if any. - let environ = this.machine.env_vars.environ.as_ref().unwrap().clone(); - let old_vars_ptr = this.read_pointer(&environ)?; - if !this.ptr_is_null(old_vars_ptr)? { - this.deallocate_ptr(old_vars_ptr, None, MiriMemoryKind::Runtime.into())?; - } - - // Collect all the pointers to each variable in a vector. - let mut vars: Vec>> = - this.machine.env_vars.map.values().copied().collect(); - // Add the trailing null pointer. - vars.push(Pointer::null()); - // Make an array with all these pointers inside Miri. - let tcx = this.tcx; - let vars_layout = this.layout_of(Ty::new_array( - tcx.tcx, - this.machine.layouts.mut_raw_ptr.ty, - u64::try_from(vars.len()).unwrap(), - ))?; - let vars_place = this.allocate(vars_layout, MiriMemoryKind::Runtime.into())?; - for (idx, var) in vars.into_iter().enumerate() { - let place = this.project_field(&vars_place, idx)?; - this.write_pointer(var, &place)?; - } - this.write_pointer(vars_place.ptr(), &environ)?; - - Ok(()) - } - - /// Reads from the `environ` static. - /// We don't actually care about the result, but we care about this potentially causing a data race. - fn read_environ(&self) -> InterpResult<'tcx> { - let this = self.eval_context_ref(); - let environ = this.machine.env_vars.environ.as_ref().unwrap(); - let _vars_ptr = this.read_pointer(environ)?; - Ok(()) - } - - fn getpid(&mut self) -> InterpResult<'tcx, i32> { - let this = self.eval_context_mut(); - this.assert_target_os_is_unix("getpid"); - - this.check_no_isolation("`getpid`")?; - - // The reason we need to do this wacky of a conversion is because - // `libc::getpid` returns an i32, however, `std::process::id()` return an u32. - // So we un-do the conversion that stdlib does and turn it back into an i32. - #[allow(clippy::cast_possible_wrap)] - Ok(std::process::id() as i32) - } - - #[allow(non_snake_case)] - fn GetCurrentProcessId(&mut self) -> InterpResult<'tcx, u32> { - let this = self.eval_context_mut(); - this.assert_target_os("windows", "GetCurrentProcessId"); - this.check_no_isolation("`GetCurrentProcessId`")?; - - Ok(std::process::id()) - } - - #[allow(non_snake_case)] - fn GetUserProfileDirectoryW( - &mut self, - token: &OpTy<'tcx, Provenance>, // HANDLE - buf: &OpTy<'tcx, Provenance>, // LPWSTR - size: &OpTy<'tcx, Provenance>, // LPDWORD - ) -> InterpResult<'tcx, Scalar> // returns BOOL - { - let this = self.eval_context_mut(); - this.assert_target_os("windows", "GetUserProfileDirectoryW"); - this.check_no_isolation("`GetUserProfileDirectoryW`")?; - - let token = this.read_target_isize(token)?; - let buf = this.read_pointer(buf)?; - let size = this.deref_pointer(size)?; - - if token != -4 { - throw_unsup_format!( - "GetUserProfileDirectoryW: only CURRENT_PROCESS_TOKEN is supported" - ); - } - - // See for docs. - Ok(match directories::UserDirs::new() { - Some(dirs) => { - let home = dirs.home_dir(); - let size_avail = if this.ptr_is_null(size.ptr())? { - 0 // if the buf pointer is null, we can't write to it; `size` will be updated to the required length - } else { - this.read_scalar(&size)?.to_u32()? - }; - // 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. - this.write_scalar(Scalar::from_u32(len.try_into().unwrap()), &size)?; - if success { - Scalar::from_i32(1) // return TRUE - } else { - this.set_last_error(this.eval_windows("c", "ERROR_INSUFFICIENT_BUFFER"))?; - Scalar::from_i32(0) // return FALSE - } - } - None => { - // We have to pick some error code. - this.set_last_error(this.eval_windows("c", "ERROR_BAD_USER_PROFILE"))?; - Scalar::from_i32(0) // return FALSE - } - }) - } -} +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {} diff --git a/src/tools/miri/src/shims/extern_static.rs b/src/tools/miri/src/shims/extern_static.rs index 7c4a54fb461..442338a6117 100644 --- a/src/tools/miri/src/shims/extern_static.rs +++ b/src/tools/miri/src/shims/extern_static.rs @@ -47,20 +47,14 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> { &["__cxa_thread_atexit_impl", "getrandom", "statx", "__clock_gettime64"], )?; // "environ" - Self::add_extern_static( - this, - "environ", - this.machine.env_vars.environ.as_ref().unwrap().ptr(), - ); + let environ = this.machine.env_vars.unix().environ(); + Self::add_extern_static(this, "environ", environ); } "freebsd" => { Self::null_ptr_extern_statics(this, &["__cxa_thread_atexit_impl"])?; // "environ" - Self::add_extern_static( - this, - "environ", - this.machine.env_vars.environ.as_ref().unwrap().ptr(), - ); + let environ = this.machine.env_vars.unix().environ(); + Self::add_extern_static(this, "environ", environ); } "android" => { Self::null_ptr_extern_statics(this, &["bsd_signal"])?; diff --git a/src/tools/miri/src/shims/unix/env.rs b/src/tools/miri/src/shims/unix/env.rs new file mode 100644 index 00000000000..128f0dcafa9 --- /dev/null +++ b/src/tools/miri/src/shims/unix/env.rs @@ -0,0 +1,276 @@ +use std::env; +use std::ffi::{OsStr, OsString}; +use std::io::ErrorKind; +use std::mem; + +use rustc_data_structures::fx::FxHashMap; +use rustc_middle::ty::layout::LayoutOf; +use rustc_middle::ty::Ty; +use rustc_target::abi::Size; + +use crate::*; + +pub struct UnixEnvVars<'tcx> { + /// Stores pointers to the environment variables. These variables must be stored as + /// null-terminated target strings (c_str or wide_str) with the `"{name}={value}"` format. + map: FxHashMap>>, + + /// Place where the `environ` static is stored. Lazily initialized, but then never changes. + environ: MPlaceTy<'tcx, Provenance>, +} + +impl VisitProvenance for UnixEnvVars<'_> { + fn visit_provenance(&self, visit: &mut VisitWith<'_>) { + let UnixEnvVars { map, environ } = self; + + environ.visit_provenance(visit); + for ptr in map.values() { + ptr.visit_provenance(visit); + } + } +} + +impl<'tcx> UnixEnvVars<'tcx> { + pub(crate) fn new<'mir>( + ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>, + env_vars: FxHashMap, + ) -> InterpResult<'tcx, Self> { + // Allocate memory for all these env vars. + let mut env_vars_machine = FxHashMap::default(); + for (name, val) in env_vars.into_iter() { + let ptr = alloc_env_var(ecx, &name, &val)?; + env_vars_machine.insert(name, ptr); + } + + // This is memory backing an extern static, hence `ExternStatic`, not `Env`. + let layout = ecx.machine.layouts.mut_raw_ptr; + let environ = ecx.allocate(layout, MiriMemoryKind::ExternStatic.into())?; + let environ_block = alloc_environ_block(ecx, env_vars_machine.values().copied().collect())?; + ecx.write_pointer(environ_block, &environ)?; + + Ok(UnixEnvVars { map: env_vars_machine, environ }) + } + + pub(crate) fn cleanup<'mir>( + ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>, + ) -> InterpResult<'tcx> { + // Deallocate individual env vars. + let env_vars = mem::take(&mut ecx.machine.env_vars.unix_mut().map); + for (_name, ptr) in env_vars { + ecx.deallocate_ptr(ptr, None, MiriMemoryKind::Runtime.into())?; + } + // Deallocate environ var list. + let environ = &ecx.machine.env_vars.unix().environ; + let old_vars_ptr = ecx.read_pointer(environ)?; + ecx.deallocate_ptr(old_vars_ptr, None, MiriMemoryKind::Runtime.into())?; + + Ok(()) + } + + pub(crate) fn environ(&self) -> Pointer> { + self.environ.ptr() + } +} + +fn alloc_env_var<'mir, 'tcx>( + ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>, + name: &OsStr, + value: &OsStr, +) -> InterpResult<'tcx, Pointer>> { + let mut name_osstring = name.to_os_string(); + name_osstring.push("="); + name_osstring.push(value); + ecx.alloc_os_str_as_c_str(name_osstring.as_os_str(), MiriMemoryKind::Runtime.into()) +} + +/// Allocates an `environ` block with the given list of pointers. +fn alloc_environ_block<'mir, 'tcx>( + ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>, + mut vars: Vec>>, +) -> InterpResult<'tcx, Pointer>> { + // Add trailing null. + vars.push(Pointer::null()); + // Make an array with all these pointers inside Miri. + let vars_layout = ecx.layout_of(Ty::new_array( + *ecx.tcx, + ecx.machine.layouts.mut_raw_ptr.ty, + u64::try_from(vars.len()).unwrap(), + ))?; + let vars_place = ecx.allocate(vars_layout, MiriMemoryKind::Runtime.into())?; + for (idx, var) in vars.into_iter().enumerate() { + let place = ecx.project_field(&vars_place, idx)?; + ecx.write_pointer(var, &place)?; + } + Ok(vars_place.ptr()) +} + +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + fn getenv( + &mut self, + name_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, Pointer>> { + let this = self.eval_context_mut(); + this.assert_target_os_is_unix("getenv"); + + let name_ptr = this.read_pointer(name_op)?; + let name = this.read_os_str_from_c_str(name_ptr)?; + + // We don't care about the value as we have the `map` to keep track of everything, + // but we do want to do this read so it shows up as a data race. + let _vars_ptr = this.read_pointer(&this.machine.env_vars.unix().environ)?; + Ok(match this.machine.env_vars.unix().map.get(name) { + Some(var_ptr) => { + // The offset is used to strip the "{name}=" part of the string. + var_ptr.offset( + Size::from_bytes(u64::try_from(name.len()).unwrap().checked_add(1).unwrap()), + this, + )? + } + None => Pointer::null(), + }) + } + + fn setenv( + &mut self, + name_op: &OpTy<'tcx, Provenance>, + value_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + this.assert_target_os_is_unix("setenv"); + + let name_ptr = this.read_pointer(name_op)?; + let value_ptr = this.read_pointer(value_op)?; + + let mut new = None; + if !this.ptr_is_null(name_ptr)? { + let name = this.read_os_str_from_c_str(name_ptr)?; + if !name.is_empty() && !name.to_string_lossy().contains('=') { + let value = this.read_os_str_from_c_str(value_ptr)?; + new = Some((name.to_owned(), value.to_owned())); + } + } + if let Some((name, value)) = new { + let var_ptr = alloc_env_var(this, &name, &value)?; + if let Some(var) = this.machine.env_vars.unix_mut().map.insert(name, var_ptr) { + this.deallocate_ptr(var, None, MiriMemoryKind::Runtime.into())?; + } + this.update_environ()?; + Ok(0) // return zero on success + } else { + // name argument is a null pointer, points to an empty string, or points to a string containing an '=' character. + let einval = this.eval_libc("EINVAL"); + this.set_last_error(einval)?; + Ok(-1) + } + } + + fn unsetenv(&mut self, name_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + this.assert_target_os_is_unix("unsetenv"); + + let name_ptr = this.read_pointer(name_op)?; + let mut success = None; + if !this.ptr_is_null(name_ptr)? { + let name = this.read_os_str_from_c_str(name_ptr)?.to_owned(); + if !name.is_empty() && !name.to_string_lossy().contains('=') { + success = Some(this.machine.env_vars.unix_mut().map.remove(&name)); + } + } + if let Some(old) = success { + if let Some(var) = old { + this.deallocate_ptr(var, None, MiriMemoryKind::Runtime.into())?; + } + this.update_environ()?; + Ok(0) + } else { + // name argument is a null pointer, points to an empty string, or points to a string containing an '=' character. + let einval = this.eval_libc("EINVAL"); + this.set_last_error(einval)?; + Ok(-1) + } + } + + fn getcwd( + &mut self, + buf_op: &OpTy<'tcx, Provenance>, + size_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, Pointer>> { + let this = self.eval_context_mut(); + this.assert_target_os_is_unix("getcwd"); + + let buf = this.read_pointer(buf_op)?; + let size = this.read_target_usize(size_op)?; + + if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { + this.reject_in_isolation("`getcwd`", reject_with)?; + this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?; + return Ok(Pointer::null()); + } + + // If we cannot get the current directory, we return null + match env::current_dir() { + Ok(cwd) => { + if this.write_path_to_c_str(&cwd, buf, size)?.0 { + return Ok(buf); + } + let erange = this.eval_libc("ERANGE"); + this.set_last_error(erange)?; + } + Err(e) => this.set_last_error_from_io_error(e.kind())?, + } + + Ok(Pointer::null()) + } + + fn chdir(&mut self, path_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + this.assert_target_os_is_unix("chdir"); + + let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?; + + if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { + this.reject_in_isolation("`chdir`", reject_with)?; + this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?; + + return Ok(-1); + } + + match env::set_current_dir(path) { + Ok(()) => Ok(0), + Err(e) => { + this.set_last_error_from_io_error(e.kind())?; + Ok(-1) + } + } + } + + /// Updates the `environ` static. + fn update_environ(&mut self) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + // Deallocate the old environ list. + let environ = this.machine.env_vars.unix().environ.clone(); + let old_vars_ptr = this.read_pointer(&environ)?; + this.deallocate_ptr(old_vars_ptr, None, MiriMemoryKind::Runtime.into())?; + + // Write the new list. + let vals = this.machine.env_vars.unix().map.values().copied().collect(); + let environ_block = alloc_environ_block(this, vals)?; + this.write_pointer(environ_block, &environ)?; + + Ok(()) + } + + fn getpid(&mut self) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + this.assert_target_os_is_unix("getpid"); + + this.check_no_isolation("`getpid`")?; + + // The reason we need to do this wacky of a conversion is because + // `libc::getpid` returns an i32, however, `std::process::id()` return an u32. + // So we un-do the conversion that stdlib does and turn it back into an i32. + #[allow(clippy::cast_possible_wrap)] + Ok(std::process::id() as i32) + } +} diff --git a/src/tools/miri/src/shims/unix/macos/foreign_items.rs b/src/tools/miri/src/shims/unix/macos/foreign_items.rs index 53a02bf5e0b..66a8dce753f 100644 --- a/src/tools/miri/src/shims/unix/macos/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/macos/foreign_items.rs @@ -74,15 +74,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // Environment related shims "_NSGetEnviron" => { let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; - this.write_pointer( - this.machine - .env_vars - .environ - .as_ref() - .expect("machine must be initialized") - .ptr(), - dest, - )?; + let environ = this.machine.env_vars.unix().environ(); + this.write_pointer(environ, dest)?; } // Time related shims diff --git a/src/tools/miri/src/shims/unix/mod.rs b/src/tools/miri/src/shims/unix/mod.rs index 2bc41e1a62d..144593aa2fc 100644 --- a/src/tools/miri/src/shims/unix/mod.rs +++ b/src/tools/miri/src/shims/unix/mod.rs @@ -1,5 +1,6 @@ pub mod foreign_items; +mod env; mod fd; mod fs; mod mem; @@ -11,9 +12,11 @@ mod freebsd; mod linux; mod macos; +pub use env::UnixEnvVars; pub use fd::{FdTable, FileDescriptor}; pub use fs::DirTable; -// All the unix-specific extension traits +// All the Unix-specific extension traits +pub use env::EvalContextExt as _; pub use fd::EvalContextExt as _; pub use fs::EvalContextExt as _; pub use mem::EvalContextExt as _; diff --git a/src/tools/miri/src/shims/windows/env.rs b/src/tools/miri/src/shims/windows/env.rs new file mode 100644 index 00000000000..e91623ac871 --- /dev/null +++ b/src/tools/miri/src/shims/windows/env.rs @@ -0,0 +1,257 @@ +use std::env; +use std::ffi::OsString; +use std::io::ErrorKind; + +use rustc_data_structures::fx::FxHashMap; + +use crate::*; +use helpers::windows_check_buffer_size; + +#[derive(Default)] +pub struct WindowsEnvVars { + /// Stores the environment varialbles. + map: FxHashMap, +} + +impl VisitProvenance for WindowsEnvVars { + fn visit_provenance(&self, _visit: &mut VisitWith<'_>) { + let WindowsEnvVars { map: _ } = self; + } +} + +impl WindowsEnvVars { + pub(crate) fn new<'mir, 'tcx>( + _ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>, + env_vars: FxHashMap, + ) -> InterpResult<'tcx, Self> { + Ok(Self { map: env_vars }) + } +} + +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + #[allow(non_snake_case)] + fn GetEnvironmentVariableW( + &mut self, + name_op: &OpTy<'tcx, Provenance>, // LPCWSTR + buf_op: &OpTy<'tcx, Provenance>, // LPWSTR + size_op: &OpTy<'tcx, Provenance>, // DWORD + ) -> InterpResult<'tcx, Scalar> { + // ^ Returns DWORD (u32 on Windows) + + let this = self.eval_context_mut(); + this.assert_target_os("windows", "GetEnvironmentVariableW"); + + let name_ptr = this.read_pointer(name_op)?; + let buf_ptr = this.read_pointer(buf_op)?; + let buf_size = this.read_scalar(size_op)?.to_u32()?; // in characters + + let name = this.read_os_str_from_wide_str(name_ptr)?; + Ok(match this.machine.env_vars.windows().map.get(&name).cloned() { + Some(val) => { + Scalar::from_u32(windows_check_buffer_size(this.write_os_str_to_wide_str( + &val, + buf_ptr, + buf_size.into(), + )?)) + // This can in fact return 0. It is up to the caller to set last_error to 0 + // beforehand and check it afterwards to exclude that case. + } + None => { + let envvar_not_found = this.eval_windows("c", "ERROR_ENVVAR_NOT_FOUND"); + this.set_last_error(envvar_not_found)?; + Scalar::from_u32(0) // return zero upon failure + } + }) + } + + #[allow(non_snake_case)] + fn GetEnvironmentStringsW(&mut self) -> InterpResult<'tcx, Pointer>> { + let this = self.eval_context_mut(); + this.assert_target_os("windows", "GetEnvironmentStringsW"); + + // Info on layout of environment blocks in Windows: + // https://docs.microsoft.com/en-us/windows/win32/procthread/environment-variables + let mut env_vars = std::ffi::OsString::new(); + for (name, value) in this.machine.env_vars.windows().map.iter() { + env_vars.push(name); + env_vars.push("="); + env_vars.push(value); + env_vars.push("\0"); + } + // Allocate environment block & Store environment variables to environment block. + // Final null terminator(block terminator) is added by `alloc_os_str_to_wide_str`. + let envblock_ptr = + this.alloc_os_str_as_wide_str(&env_vars, MiriMemoryKind::Runtime.into())?; + // If the function succeeds, the return value is a pointer to the environment block of the current process. + Ok(envblock_ptr) + } + + #[allow(non_snake_case)] + fn FreeEnvironmentStringsW( + &mut self, + env_block_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + this.assert_target_os("windows", "FreeEnvironmentStringsW"); + + let env_block_ptr = this.read_pointer(env_block_op)?; + this.deallocate_ptr(env_block_ptr, None, MiriMemoryKind::Runtime.into())?; + // If the function succeeds, the return value is nonzero. + Ok(Scalar::from_i32(1)) + } + + #[allow(non_snake_case)] + fn SetEnvironmentVariableW( + &mut self, + name_op: &OpTy<'tcx, Provenance>, // LPCWSTR + value_op: &OpTy<'tcx, Provenance>, // LPCWSTR + ) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + this.assert_target_os("windows", "SetEnvironmentVariableW"); + + let name_ptr = this.read_pointer(name_op)?; + let value_ptr = this.read_pointer(value_op)?; + + if this.ptr_is_null(name_ptr)? { + // ERROR CODE is not clearly explained in docs.. For now, throw UB instead. + throw_ub_format!("pointer to environment variable name is NULL"); + } + + let name = this.read_os_str_from_wide_str(name_ptr)?; + if name.is_empty() { + throw_unsup_format!("environment variable name is an empty string"); + } else if name.to_string_lossy().contains('=') { + throw_unsup_format!("environment variable name contains '='"); + } else if this.ptr_is_null(value_ptr)? { + // Delete environment variable `{name}` if it exists. + this.machine.env_vars.windows_mut().map.remove(&name); + Ok(this.eval_windows("c", "TRUE")) + } else { + let value = this.read_os_str_from_wide_str(value_ptr)?; + this.machine.env_vars.windows_mut().map.insert(name, value); + Ok(this.eval_windows("c", "TRUE")) + } + } + + #[allow(non_snake_case)] + fn GetCurrentDirectoryW( + &mut self, + size_op: &OpTy<'tcx, Provenance>, // DWORD + buf_op: &OpTy<'tcx, Provenance>, // LPTSTR + ) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + this.assert_target_os("windows", "GetCurrentDirectoryW"); + + let size = u64::from(this.read_scalar(size_op)?.to_u32()?); + let buf = this.read_pointer(buf_op)?; + + if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { + this.reject_in_isolation("`GetCurrentDirectoryW`", reject_with)?; + this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?; + return Ok(Scalar::from_u32(0)); + } + + // If we cannot get the current directory, we return 0 + match env::current_dir() { + Ok(cwd) => { + // This can in fact return 0. It is up to the caller to set last_error to 0 + // beforehand and check it afterwards to exclude that case. + return Ok(Scalar::from_u32(windows_check_buffer_size( + this.write_path_to_wide_str(&cwd, buf, size)?, + ))); + } + Err(e) => this.set_last_error_from_io_error(e.kind())?, + } + Ok(Scalar::from_u32(0)) + } + + #[allow(non_snake_case)] + fn SetCurrentDirectoryW( + &mut self, + path_op: &OpTy<'tcx, Provenance>, // LPCTSTR + ) -> InterpResult<'tcx, Scalar> { + // ^ Returns BOOL (i32 on Windows) + + let this = self.eval_context_mut(); + this.assert_target_os("windows", "SetCurrentDirectoryW"); + + let path = this.read_path_from_wide_str(this.read_pointer(path_op)?)?; + + if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { + this.reject_in_isolation("`SetCurrentDirectoryW`", reject_with)?; + this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?; + + return Ok(this.eval_windows("c", "FALSE")); + } + + match env::set_current_dir(path) { + Ok(()) => Ok(this.eval_windows("c", "TRUE")), + Err(e) => { + this.set_last_error_from_io_error(e.kind())?; + Ok(this.eval_windows("c", "FALSE")) + } + } + } + + #[allow(non_snake_case)] + fn GetCurrentProcessId(&mut self) -> InterpResult<'tcx, u32> { + let this = self.eval_context_mut(); + this.assert_target_os("windows", "GetCurrentProcessId"); + this.check_no_isolation("`GetCurrentProcessId`")?; + + Ok(std::process::id()) + } + + #[allow(non_snake_case)] + fn GetUserProfileDirectoryW( + &mut self, + token: &OpTy<'tcx, Provenance>, // HANDLE + buf: &OpTy<'tcx, Provenance>, // LPWSTR + size: &OpTy<'tcx, Provenance>, // LPDWORD + ) -> InterpResult<'tcx, Scalar> // returns BOOL + { + let this = self.eval_context_mut(); + this.assert_target_os("windows", "GetUserProfileDirectoryW"); + this.check_no_isolation("`GetUserProfileDirectoryW`")?; + + let token = this.read_target_isize(token)?; + let buf = this.read_pointer(buf)?; + let size = this.deref_pointer(size)?; + + if token != -4 { + throw_unsup_format!( + "GetUserProfileDirectoryW: only CURRENT_PROCESS_TOKEN is supported" + ); + } + + // See for docs. + Ok(match directories::UserDirs::new() { + Some(dirs) => { + let home = dirs.home_dir(); + let size_avail = if this.ptr_is_null(size.ptr())? { + 0 // if the buf pointer is null, we can't write to it; `size` will be updated to the required length + } else { + this.read_scalar(&size)?.to_u32()? + }; + // 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. + this.write_scalar(Scalar::from_u32(len.try_into().unwrap()), &size)?; + if success { + Scalar::from_i32(1) // return TRUE + } else { + this.set_last_error(this.eval_windows("c", "ERROR_INSUFFICIENT_BUFFER"))?; + Scalar::from_i32(0) // return FALSE + } + } + None => { + // We have to pick some error code. + this.set_last_error(this.eval_windows("c", "ERROR_BAD_USER_PROFILE"))?; + Scalar::from_i32(0) // return FALSE + } + }) + } +} diff --git a/src/tools/miri/src/shims/windows/foreign_items.rs b/src/tools/miri/src/shims/windows/foreign_items.rs index 24f7cd18e7a..e8ae80261c6 100644 --- a/src/tools/miri/src/shims/windows/foreign_items.rs +++ b/src/tools/miri/src/shims/windows/foreign_items.rs @@ -10,11 +10,10 @@ use rustc_target::spec::abi::Abi; use crate::shims::alloc::EvalContextExt as _; use crate::shims::os_str::bytes_to_os_str; +use crate::shims::windows::*; use crate::*; use shims::foreign_items::EmulateForeignItemResult; -use shims::windows::handle::{EvalContextExt as _, Handle, PseudoHandle}; -use shims::windows::sync::EvalContextExt as _; -use shims::windows::thread::EvalContextExt as _; +use shims::windows::handle::{Handle, PseudoHandle}; fn is_dyn_sym(name: &str) -> bool { // std does dynamic detection for these symbols diff --git a/src/tools/miri/src/shims/windows/mod.rs b/src/tools/miri/src/shims/windows/mod.rs index 7688abe412b..65f682b9dad 100644 --- a/src/tools/miri/src/shims/windows/mod.rs +++ b/src/tools/miri/src/shims/windows/mod.rs @@ -1,5 +1,13 @@ pub mod foreign_items; +mod env; mod handle; mod sync; mod thread; + +pub use env::WindowsEnvVars; +// All the Windows-specific extension traits +pub use env::EvalContextExt as _; +pub use handle::EvalContextExt as _; +pub use sync::EvalContextExt as _; +pub use thread::EvalContextExt as _;