about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2024-04-15 15:13:43 +0000
committerbors <bors@rust-lang.org>2024-04-15 15:13:43 +0000
commit0dee2b13ebd1e7cf9593a7ab04c8ec69404f9476 (patch)
tree116906a48813c589bc9256f6e9b74b876c564e21
parent69e8e2d800705e572653e166e6e1c58566759b66 (diff)
parente928fc58434e89f39a262378ea291b7a1bed6680 (diff)
downloadrust-0dee2b13ebd1e7cf9593a7ab04c8ec69404f9476.tar.gz
rust-0dee2b13ebd1e7cf9593a7ab04c8ec69404f9476.zip
Auto merge of #3466 - RalfJung:GetFullPathNameW, r=RalfJung
add some basic support for GetFullPathNameW

This is the last missing piece to make std `path::` tests work on Windows.
-rw-r--r--src/tools/miri/src/helpers.rs15
-rw-r--r--src/tools/miri/src/lib.rs1
-rw-r--r--src/tools/miri/src/shims/env.rs25
-rw-r--r--src/tools/miri/src/shims/os_str.rs24
-rw-r--r--src/tools/miri/src/shims/windows/foreign_items.rs93
-rw-r--r--src/tools/miri/tests/pass/shims/path.rs38
6 files changed, 174 insertions, 22 deletions
diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs
index 84eb5f832dd..998de80a7eb 100644
--- a/src/tools/miri/src/helpers.rs
+++ b/src/tools/miri/src/helpers.rs
@@ -1285,3 +1285,18 @@ pub(crate) fn simd_element_to_bool(elem: ImmTy<'_, Provenance>) -> InterpResult<
         _ => throw_ub_format!("each element of a SIMD mask must be all-0-bits or all-1-bits"),
     })
 }
+
+/// Check whether an operation that writes to a target buffer was successful.
+/// Accordingly select return value.
+/// Local helper function to be used in Windows shims.
+pub(crate) fn windows_check_buffer_size((success, len): (bool, u64)) -> u32 {
+    if success {
+        // If the function succeeds, the return value is the number of characters stored in the target buffer,
+        // not including the terminating null character.
+        u32::try_from(len.checked_sub(1).unwrap()).unwrap()
+    } else {
+        // If the target buffer was not large enough to hold the data, the return value is the buffer size, in characters,
+        // required to hold the string and its terminating null character.
+        u32::try_from(len).unwrap()
+    }
+}
diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs
index 7821aa9efd4..484908086ac 100644
--- a/src/tools/miri/src/lib.rs
+++ b/src/tools/miri/src/lib.rs
@@ -13,6 +13,7 @@
 #![feature(let_chains)]
 #![feature(lint_reasons)]
 #![feature(trait_upcasting)]
+#![feature(absolute_path)]
 // Configure clippy and other lints
 #![allow(
     clippy::collapsible_else_if,
diff --git a/src/tools/miri/src/shims/env.rs b/src/tools/miri/src/shims/env.rs
index 9e8239cdbac..1779189c9ce 100644
--- a/src/tools/miri/src/shims/env.rs
+++ b/src/tools/miri/src/shims/env.rs
@@ -9,21 +9,7 @@ use rustc_middle::ty::Ty;
 use rustc_target::abi::Size;
 
 use crate::*;
-
-/// Check whether an operation that writes to a target buffer was successful.
-/// Accordingly select return value.
-/// Local helper function to be used in Windows shims.
-fn windows_check_buffer_size((success, len): (bool, u64)) -> u32 {
-    if success {
-        // If the function succeeds, the return value is the number of characters stored in the target buffer,
-        // not including the terminating null character.
-        u32::try_from(len.checked_sub(1).unwrap()).unwrap()
-    } else {
-        // If the target buffer was not large enough to hold the data, the return value is the buffer size, in characters,
-        // required to hold the string and its terminating null character.
-        u32::try_from(len).unwrap()
-    }
-}
+use helpers::windows_check_buffer_size;
 
 #[derive(Default)]
 pub struct EnvVars<'tcx> {
@@ -164,7 +150,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
         let name_ptr = this.read_pointer(name_op)?;
         let name = this.read_os_str_from_wide_str(name_ptr)?;
         Ok(match this.machine.env_vars.map.get(&name) {
-            Some(var_ptr) => {
+            Some(&var_ptr) => {
+                this.set_last_error(Scalar::from_u32(0))?; // make sure this is unambiguously not an error
                 // The offset is used to strip the "{name}=" part of the string.
                 #[rustfmt::skip]
                 let name_offset_bytes = u64::try_from(name.len()).unwrap()
@@ -375,10 +362,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
 
         // If we cannot get the current directory, we return 0
         match env::current_dir() {
-            Ok(cwd) =>
+            Ok(cwd) => {
+                this.set_last_error(Scalar::from_u32(0))?; // make sure this is unambiguously not an error
                 return Ok(Scalar::from_u32(windows_check_buffer_size(
                     this.write_path_to_wide_str(&cwd, buf, size, /*truncate*/ false)?,
-                ))),
+                )));
+            }
             Err(e) => this.set_last_error_from_io_error(e.kind())?,
         }
         Ok(Scalar::from_u32(0))
diff --git a/src/tools/miri/src/shims/os_str.rs b/src/tools/miri/src/shims/os_str.rs
index 74e8d1d9ed6..0157c4845c5 100644
--- a/src/tools/miri/src/shims/os_str.rs
+++ b/src/tools/miri/src/shims/os_str.rs
@@ -316,9 +316,21 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
             // We also have to ensure that absolute paths remain absolute.
             match direction {
                 PathConversion::HostToTarget => {
-                    // If this start withs a `\`, we add `\\?` so it starts with `\\?\` which is
-                    // some magic path on Windows that *is* considered absolute.
-                    if converted.get(0).copied() == Some(b'\\') {
+                    // If the path is `/C:/`, the leading backslash was probably added by the below
+                    // driver letter handling and we should get rid of it again.
+                    if converted.get(0).copied() == Some(b'\\')
+                        && converted.get(2).copied() == Some(b':')
+                        && converted.get(3).copied() == Some(b'\\')
+                    {
+                        converted.remove(0);
+                    }
+                    // If this start withs a `\` but not a `\\`, then for Windows this is a relative
+                    // path. But the host path is absolute as it started with `/`. We add `\\?` so
+                    // it starts with `\\?\` which is some magic path on Windows that *is*
+                    // considered absolute.
+                    else if converted.get(0).copied() == Some(b'\\')
+                        && converted.get(1).copied() != Some(b'\\')
+                    {
                         converted.splice(0..0, b"\\\\?".iter().copied());
                     }
                 }
@@ -333,6 +345,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
                         // Remove first 3 characters
                         converted.splice(0..3, std::iter::empty());
                     }
+                    // If it starts with a drive letter, convert it to an absolute Unix path.
+                    else if converted.get(1).copied() == Some(b':')
+                        && converted.get(2).copied() == Some(b'/')
+                    {
+                        converted.insert(0, b'/');
+                    }
                 }
             }
             Cow::Owned(OsString::from_vec(converted))
diff --git a/src/tools/miri/src/shims/windows/foreign_items.rs b/src/tools/miri/src/shims/windows/foreign_items.rs
index 2e514b11cb1..de80df3c80d 100644
--- a/src/tools/miri/src/shims/windows/foreign_items.rs
+++ b/src/tools/miri/src/shims/windows/foreign_items.rs
@@ -1,5 +1,7 @@
 use std::ffi::OsStr;
+use std::io;
 use std::iter;
+use std::path::{self, Path, PathBuf};
 use std::str;
 
 use rustc_span::Symbol;
@@ -21,6 +23,61 @@ fn is_dyn_sym(name: &str) -> bool {
     )
 }
 
+#[cfg(windows)]
+fn win_absolute<'tcx>(path: &Path) -> InterpResult<'tcx, io::Result<PathBuf>> {
+    // We are on Windows so we can simply lte the host do this.
+    return Ok(path::absolute(path));
+}
+
+#[cfg(unix)]
+#[allow(clippy::get_first, clippy::arithmetic_side_effects)]
+fn win_absolute<'tcx>(path: &Path) -> InterpResult<'tcx, io::Result<PathBuf>> {
+    // We are on Unix, so we need to implement parts of the logic ourselves.
+    let bytes = path.as_os_str().as_encoded_bytes();
+    // If it starts with `//` (these were backslashes but are already converted)
+    // then this is a magic special path, we just leave it unchanged.
+    if bytes.get(0).copied() == Some(b'/') && bytes.get(1).copied() == Some(b'/') {
+        return Ok(Ok(path.into()));
+    };
+    // Special treatment for Windows' magic filenames: they are treated as being relative to `\\.\`.
+    let magic_filenames = &[
+        "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8",
+        "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
+    ];
+    if magic_filenames.iter().any(|m| m.as_bytes() == bytes) {
+        let mut result: Vec<u8> = br"//./".into();
+        result.extend(bytes);
+        return Ok(Ok(bytes_to_os_str(&result)?.into()));
+    }
+    // Otherwise we try to do something kind of close to what Windows does, but this is probably not
+    // right in all cases. We iterate over the components between `/`, and remove trailing `.`,
+    // except that trailing `..` remain unchanged.
+    let mut result = vec![];
+    let mut bytes = bytes; // the remaining bytes to process
+    loop {
+        let len = bytes.iter().position(|&b| b == b'/').unwrap_or(bytes.len());
+        let mut component = &bytes[..len];
+        if len >= 2 && component[len - 1] == b'.' && component[len - 2] != b'.' {
+            // Strip trailing `.`
+            component = &component[..len - 1];
+        }
+        // Add this component to output.
+        result.extend(component);
+        // Prepare next iteration.
+        if len < bytes.len() {
+            // There's a component after this; add `/` and process remaining bytes.
+            result.push(b'/');
+            bytes = &bytes[len + 1..];
+            continue;
+        } else {
+            // This was the last component and it did not have a trailing `/`.
+            break;
+        }
+    }
+    // Let the host `absolute` function do working-dir handling
+    Ok(path::absolute(bytes_to_os_str(&result)?))
+}
+
 impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
 pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
     fn emulate_foreign_item_inner(
@@ -112,7 +169,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
 
                 let written = if handle == -11 || handle == -12 {
                     // stdout/stderr
-                    use std::io::{self, Write};
+                    use io::Write;
 
                     let buf_cont =
                         this.read_bytes_ptr_strip_provenance(buf, Size::from_bytes(u64::from(n)))?;
@@ -146,6 +203,40 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
                     dest,
                 )?;
             }
+            "GetFullPathNameW" => {
+                let [filename, size, buffer, filepart] =
+                    this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+                this.check_no_isolation("`GetFullPathNameW`")?;
+
+                let filename = this.read_pointer(filename)?;
+                let size = this.read_scalar(size)?.to_u32()?;
+                let buffer = this.read_pointer(buffer)?;
+                let filepart = this.read_pointer(filepart)?;
+
+                if !this.ptr_is_null(filepart)? {
+                    throw_unsup_format!("GetFullPathNameW: non-null `lpFilePart` is not supported");
+                }
+
+                let filename = this.read_path_from_wide_str(filename)?;
+                let result = match win_absolute(&filename)? {
+                    Err(err) => {
+                        this.set_last_error_from_io_error(err.kind())?;
+                        Scalar::from_u32(0) // return zero upon failure
+                    }
+                    Ok(abs_filename) => {
+                        this.set_last_error(Scalar::from_u32(0))?; // make sure this is unambiguously not an error
+                        Scalar::from_u32(helpers::windows_check_buffer_size(
+                            this.write_path_to_wide_str(
+                                &abs_filename,
+                                buffer,
+                                size.into(),
+                                /*truncate*/ false,
+                            )?,
+                        ))
+                    }
+                };
+                this.write_scalar(result, dest)?;
+            }
 
             // Allocation
             "HeapAlloc" => {
diff --git a/src/tools/miri/tests/pass/shims/path.rs b/src/tools/miri/tests/pass/shims/path.rs
new file mode 100644
index 00000000000..9fc6e7faefb
--- /dev/null
+++ b/src/tools/miri/tests/pass/shims/path.rs
@@ -0,0 +1,38 @@
+//@compile-flags: -Zmiri-disable-isolation
+#![feature(absolute_path)]
+use std::path::{absolute, Path};
+
+#[track_caller]
+fn test_absolute(in_: &str, out: &str) {
+    assert_eq!(absolute(in_).unwrap().as_os_str(), Path::new(out).as_os_str());
+}
+
+fn main() {
+    if cfg!(unix) {
+        test_absolute("/a/b/c", "/a/b/c");
+        test_absolute("/a/b/c", "/a/b/c");
+        test_absolute("/a//b/c", "/a/b/c");
+        test_absolute("//a/b/c", "//a/b/c");
+        test_absolute("///a/b/c", "/a/b/c");
+        test_absolute("/a/b/c/", "/a/b/c/");
+        test_absolute("/a/./b/../c/.././..", "/a/b/../c/../..");
+    } else if cfg!(windows) {
+        // Test that all these are unchanged
+        test_absolute(r"C:\path\to\file", r"C:\path\to\file");
+        test_absolute(r"C:\path\to\file\", r"C:\path\to\file\");
+        test_absolute(r"\\server\share\to\file", r"\\server\share\to\file");
+        test_absolute(r"\\server.\share.\to\file", r"\\server.\share.\to\file");
+        test_absolute(r"\\.\PIPE\name", r"\\.\PIPE\name");
+        test_absolute(r"\\.\C:\path\to\COM1", r"\\.\C:\path\to\COM1");
+        test_absolute(r"\\?\C:\path\to\file", r"\\?\C:\path\to\file");
+        test_absolute(r"\\?\UNC\server\share\to\file", r"\\?\UNC\server\share\to\file");
+        test_absolute(r"\\?\PIPE\name", r"\\?\PIPE\name");
+        // Verbatim paths are always unchanged, no matter what.
+        test_absolute(r"\\?\path.\to/file..", r"\\?\path.\to/file..");
+
+        test_absolute(r"C:\path..\to.\file.", r"C:\path..\to\file");
+        test_absolute(r"COM1", r"\\.\COM1");
+    } else {
+        panic!("unsupported OS");
+    }
+}