Skip to content

Commit 01bc08a

Browse files
committed
Auto merge of #1225 - JOE1994:rw_widestr, r=RalfJung
Add shims for env var emulation in Windows This PR attempts to implement the final step of the instructions laid out in #707 (comment) , and is yet a work in progress. ### STATUS - [x] Add general **_target_** methods for **read_str/alloc_str** that dispatch to either **c_str** or **wide_str** variants (**helpers.rs**) - [x] Implement shims `fn getenvironmentvariablew`/`fn setenvironmentvariablew` (`std::env::var()`, `std::env::set_var()`) - [x] Implement shim `GetEnvironmentStringsW` (`std::env::vars()`) - [x] Implement shim `FreeEnvironmentStringsW` ### ISSUES (updated on 03/21/2020) - MIRI errors while running `std::env::remove_var()` in Windows. MIRI complaining about raw pointer usage in Rust standard library [*src/libstd/sys/windows/os.rs*](#1225 (comment)). ### TODO (probably on a separate PR) - Move string helpers into a new file to avoid bloating **src/helpers.rs** too much. (**shims/os_str.rs**)
2 parents e2a9c7b + 579b3c4 commit 01bc08a

File tree

7 files changed

+141
-21
lines changed

7 files changed

+141
-21
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ Several `-Z` flags are relevant for Miri:
166166
* `-Zmiri-disable-stacked-borrows` disables checking the experimental
167167
[Stacked Borrows] aliasing rules. This can make Miri run faster, but it also
168168
means no aliasing violations will be detected.
169-
* `-Zmiri-disable-isolation` disables host host isolation. As a consequence,
169+
* `-Zmiri-disable-isolation` disables host isolation. As a consequence,
170170
the program has access to host resources such as environment variables, file
171171
systems, and randomness.
172172
* `-Zmiri-ignore-leaks` disables the memory leak checker.

src/shims/env.rs

Lines changed: 125 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,16 @@ pub struct EnvVars<'tcx> {
2323
impl<'tcx> EnvVars<'tcx> {
2424
pub(crate) fn init<'mir>(
2525
ecx: &mut InterpCx<'mir, 'tcx, Evaluator<'tcx>>,
26-
excluded_env_vars: Vec<String>,
26+
mut excluded_env_vars: Vec<String>,
2727
) -> InterpResult<'tcx> {
28+
let target_os = ecx.tcx.sess.target.target.target_os.as_str();
29+
if target_os == "windows" {
30+
// Temporary hack: Exclude `TERM` var to avoid terminfo trying to open the termcap file.
31+
// Can be removed once https://github.com/rust-lang/miri/issues/1013 is resolved.
32+
excluded_env_vars.push("TERM".to_owned());
33+
}
34+
2835
if ecx.machine.communicate {
29-
let target_os = ecx.tcx.sess.target.target.target_os.as_str();
3036
for (name, value) in env::vars() {
3137
if !excluded_env_vars.contains(&name) {
3238
let var_ptr = match target_os {
@@ -82,6 +88,82 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
8288
})
8389
}
8490

91+
#[allow(non_snake_case)]
92+
fn GetEnvironmentVariableW(
93+
&mut self,
94+
name_op: OpTy<'tcx, Tag>, // LPCWSTR
95+
buf_op: OpTy<'tcx, Tag>, // LPWSTR
96+
size_op: OpTy<'tcx, Tag>, // DWORD
97+
) -> InterpResult<'tcx, u64> {
98+
let this = self.eval_context_mut();
99+
this.assert_target_os("windows", "GetEnvironmentVariableW");
100+
101+
let name_ptr = this.read_scalar(name_op)?.not_undef()?;
102+
let name = this.read_os_str_from_wide_str(name_ptr)?;
103+
Ok(match this.machine.env_vars.map.get(&name) {
104+
Some(var_ptr) => {
105+
// The offset is used to strip the "{name}=" part of the string.
106+
let name_offset_bytes =
107+
u64::try_from(name.len()).unwrap().checked_add(1).unwrap().checked_mul(2).unwrap();
108+
let var_ptr = Scalar::from(var_ptr.offset(Size::from_bytes(name_offset_bytes), this)?);
109+
let var = this.read_os_str_from_wide_str(var_ptr)?;
110+
111+
let buf_ptr = this.read_scalar(buf_op)?.not_undef()?;
112+
// `buf_size` represents the size in characters.
113+
let buf_size = u64::try_from(this.read_scalar(size_op)?.to_u32()?).unwrap();
114+
let (success, len) = this.write_os_str_to_wide_str(&var, buf_ptr, buf_size)?;
115+
116+
if success {
117+
// If the function succeeds, the return value is the number of characters stored in the buffer pointed to by lpBuffer,
118+
// not including the terminating null character.
119+
len
120+
} else {
121+
// If lpBuffer is not large enough to hold the data, the return value is the buffer size, in characters,
122+
// required to hold the string and its terminating null character and the contents of lpBuffer are undefined.
123+
len + 1
124+
}
125+
}
126+
None => {
127+
let envvar_not_found = this.eval_path_scalar(&["std", "sys", "windows", "c", "ERROR_ENVVAR_NOT_FOUND"])?;
128+
this.set_last_error(envvar_not_found.not_undef()?)?;
129+
0 // return zero upon failure
130+
}
131+
})
132+
}
133+
134+
#[allow(non_snake_case)]
135+
fn GetEnvironmentStringsW(&mut self) -> InterpResult<'tcx, Scalar<Tag>> {
136+
let this = self.eval_context_mut();
137+
this.assert_target_os("windows", "GetEnvironmentStringsW");
138+
139+
// Info on layout of environment blocks in Windows:
140+
// https://docs.microsoft.com/en-us/windows/win32/procthread/environment-variables
141+
let mut env_vars = std::ffi::OsString::new();
142+
for &item in this.machine.env_vars.map.values() {
143+
let env_var = this.read_os_str_from_wide_str(Scalar::from(item))?;
144+
env_vars.push(env_var);
145+
env_vars.push("\0");
146+
}
147+
// Allocate environment block & Store environment variables to environment block.
148+
// Final null terminator(block terminator) is added by `alloc_os_str_to_wide_str`.
149+
// FIXME: MemoryKind should be `Machine`, blocked on https://github.com/rust-lang/rust/pull/70479.
150+
let envblock_ptr = this.alloc_os_str_as_wide_str(&env_vars, MiriMemoryKind::WinHeap.into());
151+
// If the function succeeds, the return value is a pointer to the environment block of the current process.
152+
Ok(envblock_ptr.into())
153+
}
154+
155+
#[allow(non_snake_case)]
156+
fn FreeEnvironmentStringsW(&mut self, env_block_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
157+
let this = self.eval_context_mut();
158+
this.assert_target_os("windows", "FreeEnvironmentStringsW");
159+
160+
let env_block_ptr = this.read_scalar(env_block_op)?.not_undef()?;
161+
// FIXME: MemoryKind should be `Machine`, blocked on https://github.com/rust-lang/rust/pull/70479.
162+
let result = this.memory.deallocate(this.force_ptr(env_block_ptr)?, None, MiriMemoryKind::WinHeap.into());
163+
// If the function succeeds, the return value is nonzero.
164+
Ok(result.is_ok() as i32)
165+
}
166+
85167
fn setenv(
86168
&mut self,
87169
name_op: OpTy<'tcx, Tag>,
@@ -118,6 +200,47 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
118200
}
119201
}
120202

203+
#[allow(non_snake_case)]
204+
fn SetEnvironmentVariableW(
205+
&mut self,
206+
name_op: OpTy<'tcx, Tag>, // LPCWSTR
207+
value_op: OpTy<'tcx, Tag>, // LPCWSTR
208+
) -> InterpResult<'tcx, i32> {
209+
let mut this = self.eval_context_mut();
210+
this.assert_target_os("windows", "SetEnvironmentVariableW");
211+
212+
let name_ptr = this.read_scalar(name_op)?.not_undef()?;
213+
let value_ptr = this.read_scalar(value_op)?.not_undef()?;
214+
215+
if this.is_null(name_ptr)? {
216+
// ERROR CODE is not clearly explained in docs.. For now, throw UB instead.
217+
throw_ub_format!("pointer to environment variable name is NULL");
218+
}
219+
220+
let name = this.read_os_str_from_wide_str(name_ptr)?;
221+
if name.is_empty() {
222+
throw_unsup_format!("environment variable name is an empty string");
223+
} else if name.to_string_lossy().contains('=') {
224+
throw_unsup_format!("environment variable name contains '='");
225+
} else if this.is_null(value_ptr)? {
226+
// Delete environment variable `{name}`
227+
if let Some(var) = this.machine.env_vars.map.remove(&name) {
228+
this.memory.deallocate(var, None, MiriMemoryKind::Machine.into())?;
229+
this.update_environ()?;
230+
}
231+
Ok(1) // return non-zero on success
232+
} else {
233+
let value = this.read_os_str_from_wide_str(value_ptr)?;
234+
let var_ptr = alloc_env_var_as_wide_str(&name, &value, &mut this)?;
235+
if let Some(var) = this.machine.env_vars.map.insert(name, var_ptr) {
236+
this.memory
237+
.deallocate(var, None, MiriMemoryKind::Machine.into())?;
238+
}
239+
this.update_environ()?;
240+
Ok(1) // return non-zero on success
241+
}
242+
}
243+
121244
fn unsetenv(&mut self, name_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
122245
let this = self.eval_context_mut();
123246
let target_os = &this.tcx.sess.target.target.target_os;

src/shims/foreign_items/windows.rs

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,23 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
2323

2424
// Environment related shims
2525
"GetEnvironmentVariableW" => {
26-
// args[0] : LPCWSTR lpName (32-bit ptr to a const string of 16-bit Unicode chars)
27-
// args[1] : LPWSTR lpBuffer (32-bit pointer to a string of 16-bit Unicode chars)
28-
// lpBuffer : ptr to buffer that receives contents of the env_var as a null-terminated string.
29-
// Return `# of chars` stored in the buffer pointed to by lpBuffer, excluding null-terminator.
30-
// Return 0 upon failure.
31-
32-
// This is not the env var you are looking for.
33-
this.set_last_error(Scalar::from_u32(203))?; // ERROR_ENVVAR_NOT_FOUND
34-
this.write_null(dest)?;
26+
let result = this.GetEnvironmentVariableW(args[0], args[1], args[2])?;
27+
this.write_scalar(Scalar::from_uint(result, dest.layout.size), dest)?;
3528
}
3629

3730
"SetEnvironmentVariableW" => {
38-
// args[0] : LPCWSTR lpName (32-bit ptr to a const string of 16-bit Unicode chars)
39-
// args[1] : LPCWSTR lpValue (32-bit ptr to a const string of 16-bit Unicode chars)
40-
// Return nonzero if success, else return 0.
41-
throw_unsup_format!("can't set environment variable on Windows");
31+
let result = this.SetEnvironmentVariableW(args[0], args[1])?;
32+
this.write_scalar(Scalar::from_int(result, dest.layout.size), dest)?;
33+
}
34+
35+
"GetEnvironmentStringsW" => {
36+
let result = this.GetEnvironmentStringsW()?;
37+
this.write_scalar(result, dest)?;
38+
}
39+
40+
"FreeEnvironmentStringsW" => {
41+
let result = this.FreeEnvironmentStringsW(args[0])?;
42+
this.write_scalar(Scalar::from_i32(result), dest)?;
4243
}
4344

4445
// File related shims

tests/compile-fail/environ-gets-deallocated.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//ignore-windows: TODO env var emulation stubbed out on Windows
1+
//ignore-windows: Windows does not have a global environ list that the program can access directly
22

33
#[cfg(target_os="linux")]
44
fn get_environ() -> *const *const u8 {

tests/run-pass/env-exclude.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
// ignore-windows: TODO env var emulation stubbed out on Windows
21
// compile-flags: -Zmiri-disable-isolation -Zmiri-env-exclude=MIRI_ENV_VAR_TEST
32

43
fn main() {

tests/run-pass/env-without-isolation.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
// ignore-windows: TODO env var emulation stubbed out on Windows
21
// compile-flags: -Zmiri-disable-isolation
32

43
fn main() {

tests/run-pass/env.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
//ignore-windows: TODO env var emulation stubbed out on Windows
2-
31
use std::env;
42

53
fn main() {

0 commit comments

Comments
 (0)