diff --git a/Cargo.lock b/Cargo.lock index c37afbe408f..4099b1a2832 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3364,9 +3364,9 @@ dependencies = [ [[package]] name = "rustc-build-sysroot" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26170e1d79ea32f7ccec3188dd13cfc1f18c82764a9cbc1071667c0f865a4ea" +checksum = "a9bf37423495cd3a6a9ef8c75fc4566de0d26de0ab75f36f67a426e58df5768c" dependencies = [ "anyhow", "rustc_version", diff --git a/src/tools/miri/README.md b/src/tools/miri/README.md index 60a1c1fa1dd..948f1ee6c63 100644 --- a/src/tools/miri/README.md +++ b/src/tools/miri/README.md @@ -451,36 +451,32 @@ Some native rustc `-Z` flags are also very relevant for Miri: * `-Zmir-emit-retag` controls whether `Retag` statements are emitted. Miri enables this per default because it is needed for [Stacked Borrows] and [Tree Borrows]. -Moreover, Miri recognizes some environment variables: +Moreover, Miri recognizes some environment variables (unless noted otherwise, these are supported +by all intended entry points, i.e. `cargo miri` and `./miri {test,run}`): * `MIRI_AUTO_OPS` indicates whether the automatic execution of rustfmt, clippy and toolchain setup should be skipped. If it is set to `no`, they are skipped. This is used to allow automated IDE actions to avoid the auto ops. * `MIRI_LOG`, `MIRI_BACKTRACE` control logging and backtrace printing during Miri executions, also [see "Testing the Miri driver" in `CONTRIBUTING.md`][testing-miri]. -* `MIRIFLAGS` (recognized by `cargo miri` and the test suite) defines extra - flags to be passed to Miri. -* `MIRI_LIB_SRC` defines the directory where Miri expects the sources of the - standard library that it will build and use for interpretation. This directory - must point to the `library` subdirectory of a `rust-lang/rust` repository - checkout. -* `MIRI_SYSROOT` (recognized by `cargo miri` and the Miri driver) indicates the sysroot to use. When - using `cargo miri`, this skips the automatic setup -- only set this if you do not want to use the - automatically created sysroot. For directly invoking the Miri driver, this variable (or a - `--sysroot` flag) is mandatory. When invoking `cargo miri setup`, this indicates where the sysroot - will be put. -* `MIRI_TEST_TARGET` (recognized by the test suite and the `./miri` script) indicates which target +* `MIRIFLAGS` defines extra flags to be passed to Miri. +* `MIRI_LIB_SRC` defines the directory where Miri expects the sources of the standard library that + it will build and use for interpretation. This directory must point to the `library` subdirectory + of a `rust-lang/rust` repository checkout. +* `MIRI_SYSROOT` indicates the sysroot to use. When using `cargo miri`, this skips the automatic + setup -- only set this if you do not want to use the automatically created sysroot. When invoking + `cargo miri setup`, this indicates where the sysroot will be put. +* `MIRI_TEST_TARGET` (recognized by `./miri {test,run}`) indicates which target architecture to test against. `miri` and `cargo miri` accept the `--target` flag for the same purpose. -* `MIRI_TEST_THREADS` (recognized by the test suite): set the number of threads to use for running tests. - By default the number of cores is used. -* `MIRI_NO_STD` (recognized by `cargo miri`) makes sure that the target's sysroot is built without - libstd. This allows testing and running no_std programs. - (Miri has a heuristic to detect no-std targets based on the target name; this environment variable - is only needed when that heuristic fails.) -* `RUSTC_BLESS` (recognized by the test suite and `cargo-miri-test/run-test.py`): overwrite all +* `MIRI_TEST_THREADS` (recognized by `./miri test`): set the number of threads to use for running tests. + By default, the number of cores is used. +* `MIRI_NO_STD` makes sure that the target's sysroot is built without libstd. This allows testing + and running no_std programs. (Miri has a heuristic to detect no-std targets based on the target + name; this environment variable is only needed when that heuristic fails.) +* `RUSTC_BLESS` (recognized by `./miri test` and `cargo-miri-test/run-test.py`): overwrite all `stderr` and `stdout` files instead of checking whether the output matches. -* `MIRI_SKIP_UI_CHECKS` (recognized by the test suite): don't check whether the +* `MIRI_SKIP_UI_CHECKS` (recognized by `./miri test`): don't check whether the `stderr` or `stdout` files match the actual output. The following environment variables are *internal* and must not be used by diff --git a/src/tools/miri/cargo-miri/Cargo.lock b/src/tools/miri/cargo-miri/Cargo.lock index 6841a345ce1..a1dbc85605f 100644 --- a/src/tools/miri/cargo-miri/Cargo.lock +++ b/src/tools/miri/cargo-miri/Cargo.lock @@ -194,9 +194,9 @@ dependencies = [ [[package]] name = "rustc-build-sysroot" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26170e1d79ea32f7ccec3188dd13cfc1f18c82764a9cbc1071667c0f865a4ea" +checksum = "a9bf37423495cd3a6a9ef8c75fc4566de0d26de0ab75f36f67a426e58df5768c" dependencies = [ "anyhow", "rustc_version", diff --git a/src/tools/miri/cargo-miri/Cargo.toml b/src/tools/miri/cargo-miri/Cargo.toml index 55f6b5ac7ef..b16068b6d19 100644 --- a/src/tools/miri/cargo-miri/Cargo.toml +++ b/src/tools/miri/cargo-miri/Cargo.toml @@ -18,7 +18,7 @@ directories = "5" rustc_version = "0.4" serde_json = "1.0.40" cargo_metadata = "0.18.0" -rustc-build-sysroot = "0.4.1" +rustc-build-sysroot = "0.4.6" # Enable some feature flags that dev-dependencies need but dependencies # do not. This makes `./miri install` after `./miri build` faster. diff --git a/src/tools/miri/cargo-miri/src/phases.rs b/src/tools/miri/cargo-miri/src/phases.rs index 04f3b2918b5..b774ca8fa72 100644 --- a/src/tools/miri/cargo-miri/src/phases.rs +++ b/src/tools/miri/cargo-miri/src/phases.rs @@ -172,8 +172,6 @@ pub fn phase_cargo_miri(mut args: impl Iterator) { // Forward all further arguments (not consumed by `ArgSplitFlagValue`) to cargo. cmd.args(args); - // Let it know where the Miri sysroot lives. - cmd.env("MIRI_SYSROOT", miri_sysroot); // Set `RUSTC_WRAPPER` to ourselves. Cargo will prepend that binary to its usual invocation, // i.e., the first argument is `rustc` -- which is what we use in `main` to distinguish // the two codepaths. (That extra argument is why we prefer this over setting `RUSTC`.) @@ -200,10 +198,7 @@ pub fn phase_cargo_miri(mut args: impl Iterator) { // always applied. However, buggy build scripts (https://github.com/eyre-rs/eyre/issues/84) and // also cargo (https://github.com/rust-lang/cargo/issues/10885) will invoke `rustc` even when // `RUSTC_WRAPPER` is set, bypassing the wrapper. To make sure everything is coherent, we want - // that to be the Miri driver, but acting as rustc, on the target level. (Target, rather than - // host, is needed for cross-interpretation situations.) This is not a perfect emulation of real - // rustc (it might be unable to produce binaries since the sysroot is check-only), but it's as - // close as we can get, and it's good enough for autocfg. + // that to be the Miri driver, but acting as rustc, in host mode. // // In `main`, we need the value of `RUSTC` to distinguish RUSTC_WRAPPER invocations from rustdoc // or TARGET_RUNNER invocations, so we canonicalize it here to make it exceedingly unlikely that @@ -212,7 +207,10 @@ pub fn phase_cargo_miri(mut args: impl Iterator) { // bootstrap `rustc` thing in our way! Instead, we have MIRI_HOST_SYSROOT to use for host // builds. cmd.env("RUSTC", fs::canonicalize(find_miri()).unwrap()); - cmd.env("MIRI_BE_RUSTC", "target"); // we better remember to *unset* this in the other phases! + // In case we get invoked as RUSTC without the wrapper, let's be a host rustc. This makes no + // sense for cross-interpretation situations, but without the wrapper, this will use the host + // sysroot, so asking it to behave like a target build makes even less sense. + cmd.env("MIRI_BE_RUSTC", "host"); // we better remember to *unset* this in the other phases! // Set rustdoc to us as well, so we can run doctests. if let Some(orig_rustdoc) = env::var_os("RUSTDOC") { @@ -220,6 +218,8 @@ pub fn phase_cargo_miri(mut args: impl Iterator) { } cmd.env("RUSTDOC", &cargo_miri_path); + // Forward some crucial information to our own re-invocations. + cmd.env("MIRI_SYSROOT", miri_sysroot); cmd.env("MIRI_LOCAL_CRATES", local_crates(&metadata)); if verbose > 0 { cmd.env("MIRI_VERBOSE", verbose.to_string()); // This makes the other phases verbose. @@ -412,6 +412,12 @@ pub fn phase_rustc(mut args: impl Iterator, phase: RustcPhase) { // Arguments are treated very differently depending on whether this crate is // for interpretation by Miri, or for use by a build script / proc macro. if target_crate { + if phase != RustcPhase::Setup { + // Set the sysroot -- except during setup, where we don't have an existing sysroot yet + // and where the bootstrap wrapper adds its own `--sysroot` flag so we can't set ours. + cmd.arg("--sysroot").arg(env::var_os("MIRI_SYSROOT").unwrap()); + } + // Forward arguments, but patched. let emit_flag = "--emit"; // This hack helps bootstrap run standard library tests in Miri. The issue is as follows: @@ -574,6 +580,12 @@ pub fn phase_runner(mut binary_args: impl Iterator, phase: Runner cmd.env(name, val); } + if phase != RunnerPhase::Rustdoc { + // Set the sysroot. Not necessary in rustdoc, where we already set the sysroot in + // `phase_rustdoc`. rustdoc will forward that flag when invoking rustc (i.e., us), so the + // flag is present in `info.args`. + cmd.arg("--sysroot").arg(env::var_os("MIRI_SYSROOT").unwrap()); + } // Forward rustc arguments. // We need to patch "--extern" filenames because we forced a check-only // build without cargo knowing about that: replace `.rlib` suffix by diff --git a/src/tools/miri/miri-script/src/commands.rs b/src/tools/miri/miri-script/src/commands.rs index 55b3b62819f..66707dee5e7 100644 --- a/src/tools/miri/miri-script/src/commands.rs +++ b/src/tools/miri/miri-script/src/commands.rs @@ -2,6 +2,7 @@ use std::env; use std::ffi::OsString; use std::io::Write; use std::ops::Not; +use std::path::PathBuf; use std::process; use std::thread; use std::time; @@ -20,10 +21,11 @@ const JOSH_FILTER: &str = const JOSH_PORT: &str = "42042"; impl MiriEnv { - fn build_miri_sysroot(&mut self, quiet: bool) -> Result<()> { - if self.sh.var("MIRI_SYSROOT").is_ok() { + /// Returns the location of the sysroot. + fn build_miri_sysroot(&mut self, quiet: bool) -> Result { + if let Some(miri_sysroot) = self.sh.var_os("MIRI_SYSROOT") { // Sysroot already set, use that. - return Ok(()); + return Ok(miri_sysroot.into()); } let manifest_path = path!(self.miri_dir / "cargo-miri" / "Cargo.toml"); let Self { toolchain, cargo_extra_flags, .. } = &self; @@ -57,8 +59,8 @@ impl MiriEnv { .with_context(|| "`cargo miri setup` failed")?; panic!("`cargo miri setup` didn't fail again the 2nd time?"); }; - self.sh.set_var("MIRI_SYSROOT", output); - Ok(()) + self.sh.set_var("MIRI_SYSROOT", &output); + Ok(output.into()) } } @@ -505,8 +507,10 @@ impl Command { flags.push("--edition=2021".into()); // keep in sync with `tests/ui.rs`.` } - // Prepare a sysroot. - e.build_miri_sysroot(/* quiet */ true)?; + // Prepare a sysroot, and add it to the flags. + let miri_sysroot = e.build_miri_sysroot(/* quiet */ true)?; + flags.push("--sysroot".into()); + flags.push(miri_sysroot.into()); // Then run the actual command. Also add MIRIFLAGS. let miri_manifest = path!(e.miri_dir / "Cargo.toml"); diff --git a/src/tools/miri/src/bin/miri.rs b/src/tools/miri/src/bin/miri.rs index 64e2c13a451..3f7a965e9df 100644 --- a/src/tools/miri/src/bin/miri.rs +++ b/src/tools/miri/src/bin/miri.rs @@ -282,25 +282,6 @@ fn run_compiler( callbacks: &mut (dyn rustc_driver::Callbacks + Send), using_internal_features: std::sync::Arc, ) -> ! { - if target_crate { - // Miri needs a custom sysroot for target crates. - // If no `--sysroot` is given, the `MIRI_SYSROOT` env var is consulted to find where - // that sysroot lives, and that is passed to rustc. - let sysroot_flag = "--sysroot"; - if !args.iter().any(|e| e.starts_with(sysroot_flag)) { - // Using the built-in default here would be plain wrong, so we *require* - // the env var to make sure things make sense. - let miri_sysroot = env::var("MIRI_SYSROOT").unwrap_or_else(|_| { - show_error!( - "Miri was invoked in 'target' mode without `MIRI_SYSROOT` or `--sysroot` being set" - ) - }); - - args.push(sysroot_flag.to_owned()); - args.push(miri_sysroot); - } - } - // Don't insert `MIRI_DEFAULT_ARGS`, in particular, `--cfg=miri`, if we are building // a "host" crate. That may cause procedural macros (and probably build scripts) to // depend on Miri-only symbols, such as `miri_resolve_frame`: diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs index 55be4a43792..e2c6769ccb5 100644 --- a/src/tools/miri/src/helpers.rs +++ b/src/tools/miri/src/helpers.rs @@ -83,6 +83,17 @@ const UNIX_IO_ERROR_TABLE: &[(&str, std::io::ErrorKind)] = { ("EAGAIN", WouldBlock), ] }; +// This mapping should match `decode_error_kind` in +// . +const WINDOWS_IO_ERROR_TABLE: &[(&str, std::io::ErrorKind)] = { + use std::io::ErrorKind::*; + // FIXME: this is still incomplete. + &[ + ("ERROR_ACCESS_DENIED", PermissionDenied), + ("ERROR_FILE_NOT_FOUND", NotFound), + ("ERROR_INVALID_PARAMETER", InvalidInput), + ] +}; /// Gets an instance for a path. /// @@ -749,20 +760,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } throw_unsup_format!("io error {:?} cannot be translated into a raw os error", err_kind) } else if target.families.iter().any(|f| f == "windows") { - // FIXME: we have to finish implementing the Windows equivalent of this. - use std::io::ErrorKind::*; - Ok(this.eval_windows( - "c", - match err_kind { - NotFound => "ERROR_FILE_NOT_FOUND", - PermissionDenied => "ERROR_ACCESS_DENIED", - _ => - throw_unsup_format!( - "io error {:?} cannot be translated into a raw os error", - err_kind - ), - }, - )) + for &(name, kind) in WINDOWS_IO_ERROR_TABLE { + if err_kind == kind { + return Ok(this.eval_windows("c", name)); + } + } + throw_unsup_format!("io error {:?} cannot be translated into a raw os error", err_kind); } else { throw_unsup_format!( "converting io::Error into errnum is unsupported for OS {}", @@ -786,8 +789,14 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { return Ok(Some(kind)); } } - // Our table is as complete as the mapping in std, so we are okay with saying "that's a - // strange one" here. + return Ok(None); + } else if target.families.iter().any(|f| f == "windows") { + let errnum = errnum.to_u32()?; + for &(name, kind) in WINDOWS_IO_ERROR_TABLE { + if errnum == this.eval_windows("c", name).to_u32()? { + return Ok(Some(kind)); + } + } return Ok(None); } else { throw_unsup_format!( @@ -1344,3 +1353,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 62ce2ee58ae..0157c4845c5 100644 --- a/src/tools/miri/src/shims/os_str.rs +++ b/src/tools/miri/src/shims/os_str.rs @@ -98,6 +98,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { /// /// 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`). + /// The return value is still `(false, length)` in that case. fn write_os_str_to_wide_str( &mut self, os_str: &OsStr, @@ -315,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()); } } @@ -332,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 9c6994cec70..de80df3c80d 100644 --- a/src/tools/miri/src/shims/windows/foreign_items.rs +++ b/src/tools/miri/src/shims/windows/foreign_items.rs @@ -1,4 +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; @@ -20,6 +23,61 @@ fn is_dyn_sym(name: &str) -> bool { ) } +#[cfg(windows)] +fn win_absolute<'tcx>(path: &Path) -> InterpResult<'tcx, io::Result> { + // 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> { + // 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 = 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( @@ -111,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)))?; @@ -145,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" => { @@ -533,6 +625,43 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { this.set_last_error(insufficient_buffer)?; } } + "FormatMessageW" => { + let [flags, module, message_id, language_id, buffer, size, arguments] = + this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + + let flags = this.read_scalar(flags)?.to_u32()?; + let _module = this.read_pointer(module)?; // seems to contain a module name + let message_id = this.read_scalar(message_id)?; + let _language_id = this.read_scalar(language_id)?.to_u32()?; + let buffer = this.read_pointer(buffer)?; + let size = this.read_scalar(size)?.to_u32()?; + let _arguments = this.read_pointer(arguments)?; + + // We only support `FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS` + // This also means `arguments` can be ignored. + if flags != 4096u32 | 512u32 { + throw_unsup_format!("FormatMessageW: unsupported flags {flags:#x}"); + } + + let error = this.try_errnum_to_io_error(message_id)?; + let formatted = match error { + Some(err) => format!("{err}"), + None => format!(""), + }; + let (complete, length) = this.write_os_str_to_wide_str( + OsStr::new(&formatted), + buffer, + size.into(), + /*trunacte*/ false, + )?; + if !complete { + // The API docs don't say what happens when the buffer is not big enough... + // Let's just bail. + throw_unsup_format!("FormatMessageW: buffer not big enough"); + } + // The return value is the number of characters stored *excluding* the null terminator. + this.write_int(length.checked_sub(1).unwrap(), dest)?; + } // Incomplete shims that we "stub out" just to get pre-main initialization code to work. // These shims are enabled only when the caller is in the standard library. diff --git a/src/tools/miri/src/shims/x86/mod.rs b/src/tools/miri/src/shims/x86/mod.rs index 2a663d300a7..615821b2e37 100644 --- a/src/tools/miri/src/shims/x86/mod.rs +++ b/src/tools/miri/src/shims/x86/mod.rs @@ -664,54 +664,35 @@ fn convert_float_to_int<'tcx>( Ok(()) } -/// Splits `left`, `right` and `dest` (which must be SIMD vectors) -/// into 128-bit chuncks. -/// -/// `left`, `right` and `dest` cannot have different types. +/// Splits `op` (which must be a SIMD vector) into 128-bit chuncks. /// /// Returns a tuple where: /// * The first element is the number of 128-bit chunks (let's call it `N`). /// * The second element is the number of elements per chunk (let's call it `M`). -/// * The third element is the `left` vector split into chunks, i.e, it's -/// type is `[[T; M]; N]`. -/// * The fourth element is the `right` vector split into chunks. -/// * The fifth element is the `dest` vector split into chunks. -fn split_simd_to_128bit_chunks<'tcx>( +/// * The third element is the `op` vector split into chunks, i.e, it's +/// type is `[[T; M]; N]` where `T` is the element type of `op`. +fn split_simd_to_128bit_chunks<'tcx, P: Projectable<'tcx, Provenance>>( this: &mut crate::MiriInterpCx<'_, 'tcx>, - left: &OpTy<'tcx, Provenance>, - right: &OpTy<'tcx, Provenance>, - dest: &MPlaceTy<'tcx, Provenance>, -) -> InterpResult< - 'tcx, - (u64, u64, MPlaceTy<'tcx, Provenance>, MPlaceTy<'tcx, Provenance>, MPlaceTy<'tcx, Provenance>), -> { - assert_eq!(dest.layout, left.layout); - assert_eq!(dest.layout, right.layout); + op: &P, +) -> InterpResult<'tcx, (u64, u64, P)> { + let simd_layout = op.layout(); + let (simd_len, element_ty) = simd_layout.ty.simd_size_and_type(this.tcx.tcx); - let (left, left_len) = this.operand_to_simd(left)?; - let (right, right_len) = this.operand_to_simd(right)?; - let (dest, dest_len) = this.mplace_to_simd(dest)?; - - assert_eq!(dest_len, left_len); - assert_eq!(dest_len, right_len); - - assert_eq!(dest.layout.size.bits() % 128, 0); - let num_chunks = dest.layout.size.bits() / 128; - assert_eq!(dest_len.checked_rem(num_chunks), Some(0)); - let items_per_chunk = dest_len.checked_div(num_chunks).unwrap(); + assert_eq!(simd_layout.size.bits() % 128, 0); + let num_chunks = simd_layout.size.bits() / 128; + let items_per_chunk = simd_len.checked_div(num_chunks).unwrap(); // Transmute to `[[T; items_per_chunk]; num_chunks]` - let element_layout = left.layout.field(this, 0); - let chunked_layout = this.layout_of(Ty::new_array( - this.tcx.tcx, - Ty::new_array(this.tcx.tcx, element_layout.ty, items_per_chunk), - num_chunks, - ))?; - let left = left.transmute(chunked_layout, this)?; - let right = right.transmute(chunked_layout, this)?; - let dest = dest.transmute(chunked_layout, this)?; + let chunked_layout = this + .layout_of(Ty::new_array( + this.tcx.tcx, + Ty::new_array(this.tcx.tcx, element_ty, items_per_chunk), + num_chunks, + )) + .unwrap(); + let chunked_op = op.transmute(chunked_layout, this)?; - Ok((num_chunks, items_per_chunk, left, right, dest)) + Ok((num_chunks, items_per_chunk, chunked_op)) } /// Horizontaly performs `which` operation on adjacent values of @@ -731,8 +712,12 @@ fn horizontal_bin_op<'tcx>( right: &OpTy<'tcx, Provenance>, dest: &MPlaceTy<'tcx, Provenance>, ) -> InterpResult<'tcx, ()> { - let (num_chunks, items_per_chunk, left, right, dest) = - split_simd_to_128bit_chunks(this, left, right, dest)?; + assert_eq!(left.layout, dest.layout); + assert_eq!(right.layout, dest.layout); + + let (num_chunks, items_per_chunk, left) = split_simd_to_128bit_chunks(this, left)?; + let (_, _, right) = split_simd_to_128bit_chunks(this, right)?; + let (_, _, dest) = split_simd_to_128bit_chunks(this, dest)?; let middle = items_per_chunk / 2; for i in 0..num_chunks { @@ -779,8 +764,12 @@ fn conditional_dot_product<'tcx>( imm: &OpTy<'tcx, Provenance>, dest: &MPlaceTy<'tcx, Provenance>, ) -> InterpResult<'tcx, ()> { - let (num_chunks, items_per_chunk, left, right, dest) = - split_simd_to_128bit_chunks(this, left, right, dest)?; + assert_eq!(left.layout, dest.layout); + assert_eq!(right.layout, dest.layout); + + let (num_chunks, items_per_chunk, left) = split_simd_to_128bit_chunks(this, left)?; + let (_, _, right) = split_simd_to_128bit_chunks(this, right)?; + let (_, _, dest) = split_simd_to_128bit_chunks(this, dest)?; let element_layout = left.layout.field(this, 0).field(this, 0); assert!(items_per_chunk <= 4); diff --git a/src/tools/miri/tests/pass/shims/fs.rs b/src/tools/miri/tests/pass/shims/fs.rs index d10faebac7d..8a500b857bc 100644 --- a/src/tools/miri/tests/pass/shims/fs.rs +++ b/src/tools/miri/tests/pass/shims/fs.rs @@ -260,7 +260,7 @@ fn test_errors() { // Opening a non-existing file should fail with a "not found" error. assert_eq!(ErrorKind::NotFound, File::open(&path).unwrap_err().kind()); // Make sure we can also format this. - format!("{0:?}: {0}", File::open(&path).unwrap_err()); + format!("{0}: {0:?}", File::open(&path).unwrap_err()); // Removing a non-existing file should fail with a "not found" error. assert_eq!(ErrorKind::NotFound, remove_file(&path).unwrap_err().kind()); // Reading the metadata of a non-existing file should fail with a "not found" error. diff --git a/src/tools/miri/tests/pass/shims/io.rs b/src/tools/miri/tests/pass/shims/io.rs index 295723957a4..d20fc75b793 100644 --- a/src/tools/miri/tests/pass/shims/io.rs +++ b/src/tools/miri/tests/pass/shims/io.rs @@ -1,7 +1,19 @@ -use std::io::IsTerminal; +use std::io::{self, IsTerminal}; fn main() { // We can't really assume that this is truly a terminal, and anyway on Windows Miri will always // return `false` here, but we can check that the call succeeds. - std::io::stdout().is_terminal(); + io::stdout().is_terminal(); + + // Ensure we can format `io::Error` created from OS errors + // (calls OS-specific error formatting functions). + let raw_os_error = if cfg!(unix) { + 22 // EINVAL (on most Unixes, anyway) + } else if cfg!(windows) { + 87 // ERROR_INVALID_PARAMETER + } else { + panic!("unsupported OS") + }; + let err = io::Error::from_raw_os_error(raw_os_error); + format!("{err}: {err:?}"); } 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"); + } +} diff --git a/src/tools/miri/tests/ui.rs b/src/tools/miri/tests/ui.rs index 7e8d1401183..ace0da01253 100644 --- a/src/tools/miri/tests/ui.rs +++ b/src/tools/miri/tests/ui.rs @@ -112,6 +112,13 @@ fn run_tests( config.program.envs.push(("RUST_BACKTRACE".into(), Some("1".into()))); // Add some flags we always want. + config.program.args.push( + format!( + "--sysroot={}", + env::var("MIRI_SYSROOT").expect("MIRI_SYSROOT must be set to run the ui test suite") + ) + .into(), + ); config.program.args.push("-Dwarnings".into()); config.program.args.push("-Dunused".into()); config.program.args.push("-Ainternal_features".into()); @@ -296,12 +303,13 @@ fn main() -> Result<()> { fn run_dep_mode(target: String, mut args: impl Iterator) -> Result<()> { let path = args.next().expect("./miri run-dep must be followed by a file name"); - let config = miri_config( + let mut config = miri_config( &target, "", Mode::Yolo { rustfix: RustfixMode::Disabled }, /* with dependencies */ true, ); + config.program.args.clear(); // remove the `--error-format` that ui_test adds by default let dep_args = config.build_dependencies()?; let mut cmd = config.program.build(&config.out_dir);