Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add helper alloc_os_str_as_c_str and use it in env_var emulation #1098

Closed
wants to merge 12 commits into from
24 changes: 20 additions & 4 deletions src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -480,31 +480,47 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
self.eval_context_mut().memory.write_bytes(scalar, bytes.iter().copied().chain(iter::once(0u8)))?;
Ok(true)
}

fn alloc_os_str_as_c_str(
&mut self,
os_str: &OsStr,
memkind: MemoryKind<MiriMemoryKind>
) -> Pointer<Tag> {
let bytes = os_str_to_bytes(os_str).unwrap();
let size = bytes.len() as u64 + 1; // Make space for `0` terminator.

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);
self.write_os_str_to_c_str(os_str, arg_place.ptr, size).unwrap();
arg_place.ptr.assert_ptr()
}
}

#[cfg(target_os = "unix")]
fn os_str_to_bytes<'tcx, 'a>(os_str: &'a OsStr) -> InterpResult<'tcx, &'a [u8]> {
pub(crate) fn os_str_to_bytes<'tcx, 'a>(os_str: &'a OsStr) -> InterpResult<'tcx, &'a [u8]> {
JOE1994 marked this conversation as resolved.
Show resolved Hide resolved
std::os::unix::ffi::OsStringExt::into_bytes(os_str)
}

#[cfg(target_os = "unix")]
fn bytes_to_os_str<'tcx, 'a>(bytes: &'a[u8]) -> InterpResult<'tcx, &'a OsStr> {
pub(crate) fn bytes_to_os_str<'tcx, 'a>(bytes: &'a[u8]) -> InterpResult<'tcx, &'a OsStr> {
Ok(std::os::unix::ffi::OsStringExt::from_bytes(bytes))
}

// 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.
#[cfg(not(target_os = "unix"))]
fn os_str_to_bytes<'tcx, 'a>(os_str: &'a OsStr) -> InterpResult<'tcx, &'a [u8]> {
pub(crate) fn os_str_to_bytes<'tcx, 'a>(os_str: &'a OsStr) -> InterpResult<'tcx, &'a [u8]> {
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())
}

#[cfg(not(target_os = "unix"))]
fn bytes_to_os_str<'tcx, 'a>(bytes: &'a [u8]) -> InterpResult<'tcx, &'a OsStr> {
pub(crate) 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))
Expand Down
45 changes: 23 additions & 22 deletions src/shims/env.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
use std::collections::HashMap;
use std::ffi::OsString;
use std::ffi::{OsString, OsStr};
use std::env;

use crate::stacked_borrows::Tag;
use crate::*;
use crate::helpers::os_str_to_bytes;

use rustc::ty::layout::Size;
use rustc_mir::interpret::{Memory, Pointer};
use rustc_mir::interpret::Pointer;

#[derive(Default)]
pub struct EnvVars {
/// Stores pointers to the environment variables. These variables must be stored as
/// null-terminated C strings with the `"{name}={value}"` format.
map: HashMap<Vec<u8>, Pointer<Tag>>,
map: HashMap<OsString, Pointer<Tag>>,
}

impl EnvVars {
Expand All @@ -27,24 +28,24 @@ impl EnvVars {
for (name, value) in env::vars() {
if !excluded_env_vars.contains(&name) {
let var_ptr =
alloc_env_var(name.as_bytes(), value.as_bytes(), &mut ecx.memory);
ecx.machine.env_vars.map.insert(name.into_bytes(), var_ptr);
alloc_env_var_as_c_str(name.as_ref(), value.as_ref(), ecx);
RalfJung marked this conversation as resolved.
Show resolved Hide resolved
ecx.machine.env_vars.map.insert(OsString::from(name), var_ptr);
}
}
}
}
}

fn alloc_env_var<'mir, 'tcx>(
name: &[u8],
value: &[u8],
memory: &mut Memory<'mir, 'tcx, Evaluator<'tcx>>,
fn alloc_env_var_as_c_str<'mir, 'tcx>(
name: &OsStr,
value: &OsStr,
ecx: &mut InterpCx<'mir, 'tcx, Evaluator<'tcx>>
) -> Pointer<Tag> {
let mut bytes = name.to_vec();
bytes.push(b'=');
bytes.extend_from_slice(value);
bytes.push(0);
memory.allocate_static_bytes(bytes.as_slice(), MiriMemoryKind::Env.into())
let mut name_osstring = name.to_os_string();
name_osstring.push("=");
name_osstring.push(value);
name_osstring.push("\u{0000}");
ecx.alloc_os_str_as_c_str(name_osstring.as_os_str(), MiriMemoryKind::Env.into())
JOE1994 marked this conversation as resolved.
Show resolved Hide resolved
}

impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
Expand All @@ -53,7 +54,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
let this = self.eval_context_mut();

let name_ptr = this.read_scalar(name_op)?.not_undef()?;
let name = this.memory.read_c_str(name_ptr)?;
let name = this.read_os_str_from_c_str(name_ptr)?;
Ok(match this.machine.env_vars.map.get(name) {
// The offset is used to strip the "{name}=" part of the string.
Some(var_ptr) => {
Expand All @@ -68,20 +69,20 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
name_op: OpTy<'tcx, Tag>,
value_op: OpTy<'tcx, Tag>,
) -> InterpResult<'tcx, i32> {
let this = self.eval_context_mut();
let mut this = self.eval_context_mut();

let name_ptr = this.read_scalar(name_op)?.not_undef()?;
let value_ptr = this.read_scalar(value_op)?.not_undef()?;
let value = this.memory.read_c_str(value_ptr)?;
let value = this.read_os_str_from_c_str(value_ptr)?;
let mut new = None;
if !this.is_null(name_ptr)? {
let name = this.memory.read_c_str(name_ptr)?;
if !name.is_empty() && !name.contains(&b'=') {
let name = this.read_os_str_from_c_str(name_ptr)?;
if !name.is_empty() && !os_str_to_bytes(&name).unwrap().contains(&b'=') {
JOE1994 marked this conversation as resolved.
Show resolved Hide resolved
new = Some((name.to_owned(), value.to_owned()));
}
}
if let Some((name, value)) = new {
let var_ptr = alloc_env_var(&name, &value, &mut this.memory);
let var_ptr = alloc_env_var_as_c_str(name.as_os_str(), value.as_os_str(), &mut this);
JOE1994 marked this conversation as resolved.
Show resolved Hide resolved
if let Some(var) = this.machine.env_vars.map.insert(name.to_owned(), var_ptr) {
this.memory
.deallocate(var, None, MiriMemoryKind::Env.into())?;
Expand All @@ -98,8 +99,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
let name_ptr = this.read_scalar(name_op)?.not_undef()?;
let mut success = None;
if !this.is_null(name_ptr)? {
let name = this.memory.read_c_str(name_ptr)?.to_owned();
if !name.is_empty() && !name.contains(&b'=') {
let name = this.read_os_str_from_c_str(name_ptr)?.to_owned();
if !name.is_empty() && !os_str_to_bytes(&name).unwrap().contains(&b'=') {
success = Some(this.machine.env_vars.map.remove(&name));
}
}
Expand Down
12 changes: 12 additions & 0 deletions src/shims/foreign_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -912,10 +912,22 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
this.write_null(dest)?;
}
"GetEnvironmentVariableW" => {
// args[0] : LPCWSTR lpName (32-bit ptr to a const string of 16-bit Unicode chars)
// args[1] : LPWSTR lpBuffer (32-bit pointer to a string of 16-bit Unicode chars)
// lpBuffer : ptr to buffer that receives contents of the env_var as a null-terminated string.
// Return `# of chars` stored in the buffer pointed to by lpBuffer, excluding null-terminator.
// Return 0 upon failure.

// This is not the env var you are looking for.
this.set_last_error(Scalar::from_u32(203))?; // ERROR_ENVVAR_NOT_FOUND
this.write_null(dest)?;
}
"SetEnvironmentVariableW" => {
// args[0] : LPCWSTR lpName (32-bit ptr to a const string of 16-bit Unicode chars)
// args[1] : LPCWSTR lpValue (32-bit ptr to a const string of 16-bit Unicode chars)
// Return nonzero if success, else return 0.
throw_unsup_format!("can't call foreign function: {}", link_name);
JOE1994 marked this conversation as resolved.
Show resolved Hide resolved
}
"GetCommandLineW" => {
this.write_scalar(this.machine.cmd_line.expect("machine must be initialized"), dest)?;
}
Expand Down