]> git.lizzy.rs Git - rust.git/commitdiff
make env::current_exe work on Windows
authorRalf Jung <post@ralfj.de>
Mon, 9 Jan 2023 12:43:06 +0000 (13:43 +0100)
committerRalf Jung <post@ralfj.de>
Mon, 9 Jan 2023 12:46:15 +0000 (13:46 +0100)
src/tools/miri/src/shims/env.rs
src/tools/miri/src/shims/os_str.rs
src/tools/miri/src/shims/windows/foreign_items.rs
src/tools/miri/tests/pass/shims/env/current_exe.rs

index e049eec57a384bb6045cbef6f3a01c0fe82b4e8b..ce24b23ca327333b95eb2c6cf0e9493599a06637 100644 (file)
@@ -166,7 +166,9 @@ fn GetEnvironmentVariableW(
                 // `buf_size` represents the size in characters.
                 let buf_size = u64::from(this.read_scalar(size_op)?.to_u32()?);
                 Scalar::from_u32(windows_check_buffer_size(
-                    this.write_os_str_to_wide_str(&var, buf_ptr, buf_size)?,
+                    this.write_os_str_to_wide_str(
+                        &var, buf_ptr, buf_size, /*truncate*/ false,
+                    )?,
                 ))
             }
             None => {
@@ -366,7 +368,7 @@ fn GetCurrentDirectoryW(
         match env::current_dir() {
             Ok(cwd) =>
                 return Ok(Scalar::from_u32(windows_check_buffer_size(
-                    this.write_path_to_wide_str(&cwd, buf, size)?,
+                    this.write_path_to_wide_str(&cwd, buf, size, /*truncate*/ false)?,
                 ))),
             Err(e) => this.set_last_error_from_io_error(e.kind())?,
         }
index 0375a228a2148833b3153ed092169c64efb0aa83..f010d4251f4799efd2007251d3a74bb3da21f0da 100644 (file)
@@ -101,17 +101,23 @@ fn write_os_str_to_c_str(
         self.eval_context_mut().write_c_str(bytes, ptr, size)
     }
 
-    /// Helper function to write an OsStr as a 0x0000-terminated u16-sequence, which is what
-    /// the Windows APIs usually handle. This function returns `Ok((false, length))` without trying
-    /// to write if `size` is not large enough to fit the contents of `os_string` plus a null
-    /// terminator. It returns `Ok((true, length))` if the writing process was successful. The
-    /// string length returned does include the null terminator. Length is measured in units of
-    /// `u16.`
+    /// Helper function to write an OsStr as a 0x0000-terminated u16-sequence, which is what the
+    /// Windows APIs usually handle.
+    ///
+    /// If `truncate == false` (the usual mode of operation), this function returns `Ok((false,
+    /// length))` without trying to write if `size` is not large enough to fit the contents of
+    /// `os_string` plus a null terminator. It returns `Ok((true, length))` if the writing process
+    /// was successful. The string length returned does include the null terminator. Length is
+    /// measured in units of `u16.`
+    ///
+    /// If `truncate == true`, then in case `size` is not large enough it *will* write the first
+    /// `size.saturating_sub(1)` many items, followed by a null terminator (if `size > 0`).
     fn write_os_str_to_wide_str(
         &mut self,
         os_str: &OsStr,
         ptr: Pointer<Option<Provenance>>,
         size: u64,
+        truncate: bool,
     ) -> InterpResult<'tcx, (bool, u64)> {
         #[cfg(windows)]
         fn os_str_to_u16vec<'tcx>(os_str: &OsStr) -> InterpResult<'tcx, Vec<u16>> {
@@ -129,7 +135,15 @@ fn os_str_to_u16vec<'tcx>(os_str: &OsStr) -> InterpResult<'tcx, Vec<u16>> {
         }
 
         let u16_vec = os_str_to_u16vec(os_str)?;
-        self.eval_context_mut().write_wide_str(&u16_vec, ptr, size)
+        let (written, size_needed) = self.eval_context_mut().write_wide_str(&u16_vec, ptr, size)?;
+        if truncate && !written && size > 0 {
+            // Write the truncated part that fits.
+            let truncated_data = &u16_vec[..size.saturating_sub(1).try_into().unwrap()];
+            let (written, written_len) =
+                self.eval_context_mut().write_wide_str(truncated_data, ptr, size)?;
+            assert!(written && written_len == size);
+        }
+        Ok((written, size_needed))
     }
 
     /// Allocate enough memory to store the given `OsStr` as a null-terminated sequence of bytes.
@@ -143,7 +157,8 @@ fn alloc_os_str_as_c_str(
 
         let arg_type = this.tcx.mk_array(this.tcx.types.u8, size);
         let arg_place = this.allocate(this.layout_of(arg_type).unwrap(), memkind)?;
-        assert!(self.write_os_str_to_c_str(os_str, arg_place.ptr, size).unwrap().0);
+        let (written, _) = self.write_os_str_to_c_str(os_str, arg_place.ptr, size).unwrap();
+        assert!(written);
         Ok(arg_place.ptr)
     }
 
@@ -158,7 +173,9 @@ fn alloc_os_str_as_wide_str(
 
         let arg_type = this.tcx.mk_array(this.tcx.types.u16, size);
         let arg_place = this.allocate(this.layout_of(arg_type).unwrap(), memkind)?;
-        assert!(self.write_os_str_to_wide_str(os_str, arg_place.ptr, size).unwrap().0);
+        let (written, _) =
+            self.write_os_str_to_wide_str(os_str, arg_place.ptr, size, /*truncate*/ false).unwrap();
+        assert!(written);
         Ok(arg_place.ptr)
     }
 
@@ -212,11 +229,12 @@ fn write_path_to_wide_str(
         path: &Path,
         ptr: Pointer<Option<Provenance>>,
         size: u64,
+        truncate: bool,
     ) -> InterpResult<'tcx, (bool, u64)> {
         let this = self.eval_context_mut();
         let os_str =
             this.convert_path(Cow::Borrowed(path.as_os_str()), PathConversion::HostToTarget);
-        this.write_os_str_to_wide_str(&os_str, ptr, size)
+        this.write_os_str_to_wide_str(&os_str, ptr, size, truncate)
     }
 
     /// Allocate enough memory to store a Path as a null-terminated sequence of bytes,
@@ -232,6 +250,19 @@ fn alloc_path_as_c_str(
         this.alloc_os_str_as_c_str(&os_str, memkind)
     }
 
+    /// Allocate enough memory to store a Path as a null-terminated sequence of `u16`s,
+    /// adjusting path separators if needed.
+    fn alloc_path_as_wide_str(
+        &mut self,
+        path: &Path,
+        memkind: MemoryKind<MiriMemoryKind>,
+    ) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
+        let this = self.eval_context_mut();
+        let os_str =
+            this.convert_path(Cow::Borrowed(path.as_os_str()), PathConversion::HostToTarget);
+        this.alloc_os_str_as_wide_str(&os_str, memkind)
+    }
+
     #[allow(clippy::get_first)]
     fn convert_path<'a>(
         &self,
index 1da8f7c0e3e433ff660ccd1172f6844d307b2bca..f310d16e86198fcae581464bdf0be90253caa106 100644 (file)
@@ -381,6 +381,46 @@ fn emulate_foreign_item_by_name(
 
                 this.write_scalar(Scalar::from_u32(1), dest)?;
             }
+            "GetModuleFileNameW" => {
+                let [handle, filename, size] =
+                    this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+                this.check_no_isolation("`GetModuleFileNameW`")?;
+
+                let handle = this.read_machine_usize(handle)?;
+                let filename = this.read_pointer(filename)?;
+                let size = this.read_scalar(size)?.to_u32()?;
+
+                if handle != 0 {
+                    throw_unsup_format!("`GetModuleFileNameW` only supports the NULL handle");
+                }
+
+                // Using the host current_exe is a bit off, but consistent with Linux
+                // (where stdlib reads /proc/self/exe).
+                // Unfortunately this Windows function has a crazy behavior so we can't just use
+                // `write_path_to_wide_str`...
+                let path = std::env::current_exe().unwrap();
+                let (all_written, size_needed) = this.write_path_to_wide_str(
+                    &path,
+                    filename,
+                    size.into(),
+                    /*truncate*/ true,
+                )?;
+
+                if all_written {
+                    // If the function succeeds, the return value is the length of the string that
+                    // is copied to the buffer, in characters, not including the terminating null
+                    // character.
+                    this.write_int(size_needed.checked_sub(1).unwrap(), dest)?;
+                } else {
+                    // If the buffer is too small to hold the module name, the string is truncated
+                    // to nSize characters including the terminating null character, the function
+                    // returns nSize, and the function sets the last error to
+                    // ERROR_INSUFFICIENT_BUFFER.
+                    this.write_int(size, dest)?;
+                    let insufficient_buffer = this.eval_windows("c", "ERROR_INSUFFICIENT_BUFFER");
+                    this.set_last_error(insufficient_buffer)?;
+                }
+            }
 
             // Threading
             "CreateThread" => {
index 3f1153d265d7e144cc007ea263f845e3f1d685d2..898a42b72d134819f25b46e0b80bda1ad6c42c88 100644 (file)
@@ -1,4 +1,3 @@
-//@ignore-target-windows: current_exe not supported on Windows
 //@only-on-host: the Linux std implementation opens /proc/self/exe, which doesn't work cross-target
 //@compile-flags: -Zmiri-disable-isolation
 use std::env;