Skip to content

Commit 9db5511

Browse files
committed
make unix path handling on Windows hosts preserve absoluteness
1 parent 4bf26df commit 9db5511

File tree

7 files changed

+119
-33
lines changed

7 files changed

+119
-33
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -581,7 +581,7 @@ extern "Rust" {
581581
/// Performs conversion of path separators as needed.
582582
///
583583
/// Usually Miri performs this kind of conversion automatically. However, manual conversion
584-
/// might be necessary when reading an environment variable that was set of the host
584+
/// might be necessary when reading an environment variable that was set on the host
585585
/// (such as TMPDIR) and using it as a target path.
586586
///
587587
/// Only works with isolation disabled.

src/shims/os_str.rs

+38-13
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
174174
let this = self.eval_context_ref();
175175
let os_str = this.read_os_str_from_c_str(ptr)?;
176176

177-
Ok(match this.convert_path_separator(Cow::Borrowed(os_str), PathConversion::TargetToHost) {
177+
Ok(match this.convert_path(Cow::Borrowed(os_str), PathConversion::TargetToHost) {
178178
Cow::Borrowed(x) => Cow::Borrowed(Path::new(x)),
179179
Cow::Owned(y) => Cow::Owned(PathBuf::from(y)),
180180
})
@@ -188,10 +188,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
188188
let this = self.eval_context_ref();
189189
let os_str = this.read_os_str_from_wide_str(ptr)?;
190190

191-
Ok(this
192-
.convert_path_separator(Cow::Owned(os_str), PathConversion::TargetToHost)
193-
.into_owned()
194-
.into())
191+
Ok(this.convert_path(Cow::Owned(os_str), PathConversion::TargetToHost).into_owned().into())
195192
}
196193

197194
/// Write a Path to the machine memory (as a null-terminated sequence of bytes),
@@ -203,8 +200,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
203200
size: u64,
204201
) -> InterpResult<'tcx, (bool, u64)> {
205202
let this = self.eval_context_mut();
206-
let os_str = this
207-
.convert_path_separator(Cow::Borrowed(path.as_os_str()), PathConversion::HostToTarget);
203+
let os_str =
204+
this.convert_path(Cow::Borrowed(path.as_os_str()), PathConversion::HostToTarget);
208205
this.write_os_str_to_c_str(&os_str, ptr, size)
209206
}
210207

@@ -217,8 +214,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
217214
size: u64,
218215
) -> InterpResult<'tcx, (bool, u64)> {
219216
let this = self.eval_context_mut();
220-
let os_str = this
221-
.convert_path_separator(Cow::Borrowed(path.as_os_str()), PathConversion::HostToTarget);
217+
let os_str =
218+
this.convert_path(Cow::Borrowed(path.as_os_str()), PathConversion::HostToTarget);
222219
this.write_os_str_to_wide_str(&os_str, ptr, size)
223220
}
224221

@@ -230,18 +227,19 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
230227
memkind: MemoryKind<MiriMemoryKind>,
231228
) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
232229
let this = self.eval_context_mut();
233-
let os_str = this
234-
.convert_path_separator(Cow::Borrowed(path.as_os_str()), PathConversion::HostToTarget);
230+
let os_str =
231+
this.convert_path(Cow::Borrowed(path.as_os_str()), PathConversion::HostToTarget);
235232
this.alloc_os_str_as_c_str(&os_str, memkind)
236233
}
237234

238-
fn convert_path_separator<'a>(
235+
fn convert_path<'a>(
239236
&self,
240237
os_str: Cow<'a, OsStr>,
241238
direction: PathConversion,
242239
) -> Cow<'a, OsStr> {
243240
let this = self.eval_context_ref();
244241
let target_os = &this.tcx.sess.target.os;
242+
245243
#[cfg(windows)]
246244
return if target_os == "windows" {
247245
// Windows-on-Windows, all fine.
@@ -252,10 +250,35 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
252250
PathConversion::HostToTarget => ('\\', '/'),
253251
PathConversion::TargetToHost => ('/', '\\'),
254252
};
255-
let converted = os_str
253+
let mut converted = os_str
256254
.encode_wide()
257255
.map(|wchar| if wchar == from as u16 { to as u16 } else { wchar })
258256
.collect::<Vec<_>>();
257+
// We also have to ensure that absolute paths remain absolute.
258+
match direction {
259+
PathConversion::HostToTarget => {
260+
// If this is an absolute Windows path that starts with a drive letter (`C:/...`
261+
// after separator conversion), it would not be considered absolute by Unix
262+
// target code.
263+
if converted.get(1).copied() == Some(':' as u16)
264+
&& converted.get(2).copied() == Some('/' as u16)
265+
{
266+
// We add a `/` at the beginning, to store the absolute Windows
267+
// path in something that looks like an absolute Unix path.
268+
converted.insert(0, '/' as u16);
269+
}
270+
}
271+
PathConversion::TargetToHost => {
272+
// If the path is `\C:\`, the leading backslash was probably added by the above code
273+
// and we should get rid of it again.
274+
if converted.get(0).copied() == Some('\\' as u16)
275+
&& converted.get(2).copied() == Some(':' as u16)
276+
&& converted.get(3).copied() == Some('\\' as u16)
277+
{
278+
converted.remove(0);
279+
}
280+
}
281+
}
259282
Cow::Owned(OsString::from_wide(&converted))
260283
};
261284
#[cfg(unix)]
@@ -270,6 +293,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
270293
.iter()
271294
.map(|&wchar| if wchar == from as u8 { to as u8 } else { wchar })
272295
.collect::<Vec<_>>();
296+
// TODO: Once we actually support file system things on Windows targets, we'll probably
297+
// have to also do something clever for absolute path preservation here, like above.
273298
Cow::Owned(OsString::from_vec(converted))
274299
} else {
275300
// Unix-on-Unix, all is fine.

src/shims/unix/fs.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1667,7 +1667,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
16671667
// 'readlink' truncates the resolved path if the provided buffer is not large
16681668
// enough, and does *not* add a null terminator. That means we cannot use the usual
16691669
// `write_path_to_c_str` and have to re-implement parts of it ourselves.
1670-
let resolved = this.convert_path_separator(
1670+
let resolved = this.convert_path(
16711671
Cow::Borrowed(resolved.as_ref()),
16721672
crate::shims::os_str::PathConversion::HostToTarget,
16731673
);

test-cargo-miri/src/main.rs

+23-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use byteorder::{BigEndian, ByteOrder};
22
use std::env;
33
#[cfg(unix)]
44
use std::io::{self, BufRead};
5+
use std::path::PathBuf;
56

67
fn main() {
78
// Check env var set by `build.rs`.
@@ -21,12 +22,30 @@ fn main() {
2122
// If there were no arguments, access stdin and test working dir.
2223
// (We rely on the test runner to always disable isolation when passing no arguments.)
2324
if std::env::args().len() <= 1 {
25+
fn host_to_target_path(path: String) -> PathBuf {
26+
use std::ffi::{CStr, CString};
27+
28+
let path = CString::new(path).unwrap();
29+
let mut out = Vec::with_capacity(1024);
30+
31+
unsafe {
32+
extern "Rust" {
33+
fn miri_host_to_target_path(
34+
path: *const i8,
35+
out: *mut i8,
36+
out_size: usize,
37+
) -> usize;
38+
}
39+
let ret = miri_host_to_target_path(path.as_ptr(), out.as_mut_ptr(), out.capacity());
40+
assert_eq!(ret, 0);
41+
let out = CStr::from_ptr(out.as_ptr()).to_str().unwrap();
42+
PathBuf::from(out)
43+
}
44+
}
45+
2446
// CWD should be crate root.
25-
// We have to normalize slashes, as the env var might be set for a different target's conventions.
2647
let env_dir = env::current_dir().unwrap();
27-
let env_dir = env_dir.to_string_lossy().replace("\\", "/");
28-
let crate_dir = env::var_os("CARGO_MANIFEST_DIR").unwrap();
29-
let crate_dir = crate_dir.to_string_lossy().replace("\\", "/");
48+
let crate_dir = host_to_target_path(env::var("CARGO_MANIFEST_DIR").unwrap());
3049
assert_eq!(env_dir, crate_dir);
3150

3251
#[cfg(unix)]

test-cargo-miri/subcrate/main.rs

+23-6
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,30 @@ use std::path::PathBuf;
44
fn main() {
55
println!("subcrate running");
66

7+
fn host_to_target_path(path: String) -> PathBuf {
8+
use std::ffi::{CStr, CString};
9+
10+
let path = CString::new(path).unwrap();
11+
let mut out = Vec::with_capacity(1024);
12+
13+
unsafe {
14+
extern "Rust" {
15+
fn miri_host_to_target_path(
16+
path: *const i8,
17+
out: *mut i8,
18+
out_size: usize,
19+
) -> usize;
20+
}
21+
let ret = miri_host_to_target_path(path.as_ptr(), out.as_mut_ptr(), out.capacity());
22+
assert_eq!(ret, 0);
23+
let out = CStr::from_ptr(out.as_ptr()).to_str().unwrap();
24+
PathBuf::from(out)
25+
}
26+
}
27+
728
// CWD should be workspace root, i.e., one level up from crate root.
8-
// We have to normalize slashes, as the env var might be set for a different target's conventions.
929
let env_dir = env::current_dir().unwrap();
10-
let env_dir = env_dir.to_string_lossy().replace("\\", "/");
11-
let crate_dir = env::var_os("CARGO_MANIFEST_DIR").unwrap();
12-
let crate_dir = crate_dir.to_string_lossy().replace("\\", "/");
13-
let crate_dir = PathBuf::from(crate_dir);
14-
let crate_dir = crate_dir.parent().unwrap().to_string_lossy();
30+
let crate_dir = host_to_target_path(env::var("CARGO_MANIFEST_DIR").unwrap());
31+
let crate_dir = crate_dir.parent().unwrap();
1532
assert_eq!(env_dir, crate_dir);
1633
}

test-cargo-miri/subcrate/test.rs

+24-3
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,37 @@
11
use std::env;
22

3+
use std::path::PathBuf;
4+
35
use byteorder::{ByteOrder, LittleEndian};
46

57
fn main() {
68
println!("subcrate testing");
79

10+
fn host_to_target_path(path: String) -> PathBuf {
11+
use std::ffi::{CStr, CString};
12+
13+
let path = CString::new(path).unwrap();
14+
let mut out = Vec::with_capacity(1024);
15+
16+
unsafe {
17+
extern "Rust" {
18+
fn miri_host_to_target_path(
19+
path: *const i8,
20+
out: *mut i8,
21+
out_size: usize,
22+
) -> usize;
23+
}
24+
let ret = miri_host_to_target_path(path.as_ptr(), out.as_mut_ptr(), out.capacity());
25+
assert_eq!(ret, 0);
26+
let out = CStr::from_ptr(out.as_ptr()).to_str().unwrap();
27+
PathBuf::from(out)
28+
}
29+
}
30+
831
// CWD should be crate root.
932
// We have to normalize slashes, as the env var might be set for a different target's conventions.
1033
let env_dir = env::current_dir().unwrap();
11-
let env_dir = env_dir.to_string_lossy().replace("\\", "/");
12-
let crate_dir = env::var_os("CARGO_MANIFEST_DIR").unwrap();
13-
let crate_dir = crate_dir.to_string_lossy().replace("\\", "/");
34+
let crate_dir = host_to_target_path(env::var("CARGO_MANIFEST_DIR").unwrap());
1435
assert_eq!(env_dir, crate_dir);
1536

1637
// Make sure we can call dev-dependencies.

tests/pass/shims/fs.rs

+9-5
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use std::io::{Error, ErrorKind, IsTerminal, Read, Result, Seek, SeekFrom, Write}
1515
use std::path::{Path, PathBuf};
1616

1717
fn main() {
18+
test_path_conversion();
1819
test_file();
1920
test_file_clone();
2021
test_file_create_new();
@@ -28,15 +29,11 @@ fn main() {
2829
test_directory();
2930
test_canonicalize();
3031
test_from_raw_os_error();
31-
test_path_conversion();
3232
}
3333

34-
fn tmp() -> PathBuf {
34+
fn host_to_target_path(path: String) -> PathBuf {
3535
use std::ffi::{CStr, CString};
3636

37-
let path = std::env::var("MIRI_TEMP")
38-
.unwrap_or_else(|_| std::env::temp_dir().into_os_string().into_string().unwrap());
39-
// These are host paths. We need to convert them to the target.
4037
let path = CString::new(path).unwrap();
4138
let mut out = Vec::with_capacity(1024);
4239

@@ -51,6 +48,13 @@ fn tmp() -> PathBuf {
5148
}
5249
}
5350

51+
fn tmp() -> PathBuf {
52+
let path = std::env::var("MIRI_TEMP")
53+
.unwrap_or_else(|_| std::env::temp_dir().into_os_string().into_string().unwrap());
54+
// These are host paths. We need to convert them to the target.
55+
host_to_target_path(path)
56+
}
57+
5458
/// Prepare: compute filename and make sure the file does not exist.
5559
fn prepare(filename: &str) -> PathBuf {
5660
let path = tmp().join(filename);

0 commit comments

Comments
 (0)