diff --git a/.gitmodules b/.gitmodules index bc2dd39774154..c850493780613 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,9 +13,6 @@ [submodule "src/doc/book"] path = src/doc/book url = https://github.com/rust-lang/book.git -[submodule "src/tools/miri"] - path = src/tools/miri - url = https://github.com/rust-lang/miri.git [submodule "src/doc/rust-by-example"] path = src/doc/rust-by-example url = https://github.com/rust-lang/rust-by-example.git diff --git a/src/README.md b/src/README.md index 3d2e6acd5764b..90ab802697088 100644 --- a/src/README.md +++ b/src/README.md @@ -2,7 +2,7 @@ This directory contains the source code of the rust project, including: - The test suite - The bootstrapping build system -- Various submodules for tools, like cargo, miri, etc. +- Various submodules for tools, like cargo, etc. For more information on how various parts of the compiler work, see the [rustc dev guide]. diff --git a/src/bootstrap/check.rs b/src/bootstrap/check.rs index 5c085bedf0eef..229851238f1d0 100644 --- a/src/bootstrap/check.rs +++ b/src/bootstrap/check.rs @@ -456,6 +456,8 @@ tool_check_step!(Rustdoc, "src/tools/rustdoc", "src/librustdoc", SourceType::InT // behavior, treat it as in-tree so that any new warnings in clippy will be // rejected. tool_check_step!(Clippy, "src/tools/clippy", SourceType::InTree); +// Miri on the other hand is treated as out of tree, since InTree also causes it to +// be run as part of `check`, which can fail on platforms which libffi-sys has no support for. tool_check_step!(Miri, "src/tools/miri", SourceType::Submodule); tool_check_step!(Rls, "src/tools/rls", SourceType::InTree); tool_check_step!(Rustfmt, "src/tools/rustfmt", SourceType::InTree); diff --git a/src/bootstrap/dist.rs b/src/bootstrap/dist.rs index c9ee3c1c7d65d..05664ca21794f 100644 --- a/src/bootstrap/dist.rs +++ b/src/bootstrap/dist.rs @@ -35,18 +35,6 @@ pub fn tmpdir(builder: &Builder<'_>) -> PathBuf { builder.out.join("tmp/dist") } -fn missing_tool(tool_name: &str, skip: bool) { - if skip { - println!("Unable to build {}, skipping dist", tool_name) - } else { - let help = "note: not all tools are available on all nightlies\nhelp: see https://forge.rust-lang.org/infra/toolstate.html for more information"; - panic!( - "Unable to build submodule tool {} (use `missing-tools = true` to ignore this failure)\n{}", - tool_name, help - ) - } -} - fn should_build_extended_tool(builder: &Builder<'_>, tool: &str) -> bool { if !builder.config.extended { return false; @@ -1209,18 +1197,9 @@ impl Step for Miri { let compiler = self.compiler; let target = self.target; - let miri = builder - .ensure(tool::Miri { compiler, target, extra_features: Vec::new() }) - .or_else(|| { - missing_tool("miri", builder.build.config.missing_tools); - None - })?; - let cargomiri = builder - .ensure(tool::CargoMiri { compiler, target, extra_features: Vec::new() }) - .or_else(|| { - missing_tool("cargo miri", builder.build.config.missing_tools); - None - })?; + let miri = builder.ensure(tool::Miri { compiler, target, extra_features: Vec::new() })?; + let cargomiri = + builder.ensure(tool::CargoMiri { compiler, target, extra_features: Vec::new() })?; let mut tarball = Tarball::new(builder, "miri", &target.triple); tarball.set_overlay(OverlayKind::Miri); @@ -1451,7 +1430,7 @@ impl Step for Extended { let xform = |p: &Path| { let mut contents = t!(fs::read_to_string(p)); - for tool in &["rust-demangler", "rust-analyzer", "miri", "rustfmt"] { + for tool in &["rust-demangler", "rust-analyzer", "rustfmt"] { if !built_tools.contains(tool) { contents = filter(&contents, tool); } @@ -1491,7 +1470,8 @@ impl Step for Extended { prepare("rust-std"); prepare("rust-analysis"); prepare("clippy"); - for tool in &["rust-docs", "rust-demangler", "rust-analyzer", "miri"] { + prepare("miri"); + for tool in &["rust-docs", "rust-demangler", "rust-analyzer"] { if built_tools.contains(tool) { prepare(tool); } @@ -1550,7 +1530,8 @@ impl Step for Extended { prepare("rust-docs"); prepare("rust-std"); prepare("clippy"); - for tool in &["rust-demangler", "rust-analyzer", "miri"] { + prepare("miri"); + for tool in &["rust-demangler", "rust-analyzer"] { if built_tools.contains(tool) { prepare(tool); } @@ -1689,25 +1670,23 @@ impl Step for Extended { .arg(etc.join("msi/remove-duplicates.xsl")), ); } - if built_tools.contains("miri") { - builder.run( - Command::new(&heat) - .current_dir(&exe) - .arg("dir") - .arg("miri") - .args(&heat_flags) - .arg("-cg") - .arg("MiriGroup") - .arg("-dr") - .arg("Miri") - .arg("-var") - .arg("var.MiriDir") - .arg("-out") - .arg(exe.join("MiriGroup.wxs")) - .arg("-t") - .arg(etc.join("msi/remove-duplicates.xsl")), - ); - } + builder.run( + Command::new(&heat) + .current_dir(&exe) + .arg("dir") + .arg("miri") + .args(&heat_flags) + .arg("-cg") + .arg("MiriGroup") + .arg("-dr") + .arg("Miri") + .arg("-var") + .arg("var.MiriDir") + .arg("-out") + .arg(exe.join("MiriGroup.wxs")) + .arg("-t") + .arg(etc.join("msi/remove-duplicates.xsl")), + ); builder.run( Command::new(&heat) .current_dir(&exe) @@ -1755,6 +1734,7 @@ impl Step for Extended { .arg("-dStdDir=rust-std") .arg("-dAnalysisDir=rust-analysis") .arg("-dClippyDir=clippy") + .arg("-dMiriDir=miri") .arg("-arch") .arg(&arch) .arg("-out") @@ -1768,9 +1748,6 @@ impl Step for Extended { if built_tools.contains("rust-analyzer") { cmd.arg("-dRustAnalyzerDir=rust-analyzer"); } - if built_tools.contains("miri") { - cmd.arg("-dMiriDir=miri"); - } if target.ends_with("windows-gnu") { cmd.arg("-dGccDir=rust-mingw"); } @@ -1784,15 +1761,13 @@ impl Step for Extended { candle("CargoGroup.wxs".as_ref()); candle("StdGroup.wxs".as_ref()); candle("ClippyGroup.wxs".as_ref()); + candle("MiriGroup.wxs".as_ref()); if built_tools.contains("rust-demangler") { candle("RustDemanglerGroup.wxs".as_ref()); } if built_tools.contains("rust-analyzer") { candle("RustAnalyzerGroup.wxs".as_ref()); } - if built_tools.contains("miri") { - candle("MiriGroup.wxs".as_ref()); - } candle("AnalysisGroup.wxs".as_ref()); if target.ends_with("windows-gnu") { @@ -1822,6 +1797,7 @@ impl Step for Extended { .arg("StdGroup.wixobj") .arg("AnalysisGroup.wixobj") .arg("ClippyGroup.wixobj") + .arg("MiriGroup.wixobj") .current_dir(&exe); if built_tools.contains("rust-analyzer") { @@ -1830,9 +1806,6 @@ impl Step for Extended { if built_tools.contains("rust-demangler") { cmd.arg("RustDemanglerGroup.wixobj"); } - if built_tools.contains("miri") { - cmd.arg("MiriGroup.wixobj"); - } if target.ends_with("windows-gnu") { cmd.arg("GccGroup.wixobj"); diff --git a/src/bootstrap/install.rs b/src/bootstrap/install.rs index d34aa15c51539..7672b7c913594 100644 --- a/src/bootstrap/install.rs +++ b/src/bootstrap/install.rs @@ -200,13 +200,10 @@ install!((self, builder, _config), install_sh(builder, "clippy", self.compiler.stage, Some(self.target), &tarball); }; Miri, alias = "miri", Self::should_build(_config), only_hosts: true, { - if let Some(tarball) = builder.ensure(dist::Miri { compiler: self.compiler, target: self.target }) { - install_sh(builder, "miri", self.compiler.stage, Some(self.target), &tarball); - } else { - builder.info( - &format!("skipping Install miri stage{} ({})", self.compiler.stage, self.target), - ); - } + let tarball = builder + .ensure(dist::Miri { compiler: self.compiler, target: self.target }) + .expect("missing miri"); + install_sh(builder, "miri", self.compiler.stage, Some(self.target), &tarball); }; Rustfmt, alias = "rustfmt", Self::should_build(_config), only_hosts: true, { if let Some(tarball) = builder.ensure(dist::Rustfmt { diff --git a/src/bootstrap/lib.rs b/src/bootstrap/lib.rs index 0464dbde065ef..b421ebc2d018c 100644 --- a/src/bootstrap/lib.rs +++ b/src/bootstrap/lib.rs @@ -542,13 +542,8 @@ impl Build { // Make sure we update these before gathering metadata so we don't get an error about missing // Cargo.toml files. - let rust_submodules = [ - "src/tools/rust-installer", - "src/tools/cargo", - "src/tools/miri", - "library/backtrace", - "library/stdarch", - ]; + let rust_submodules = + ["src/tools/rust-installer", "src/tools/cargo", "library/backtrace", "library/stdarch"]; for s in rust_submodules { build.update_submodule(Path::new(s)); } diff --git a/src/bootstrap/test.rs b/src/bootstrap/test.rs index 9d286ddd6d164..1617875ec231c 100644 --- a/src/bootstrap/test.rs +++ b/src/bootstrap/test.rs @@ -484,116 +484,103 @@ impl Step for Miri { // Except if we are at stage 2, the bootstrap loop is complete and we can stick with our current stage. let compiler_std = builder.compiler(if stage < 2 { stage + 1 } else { stage }, host); - let miri = - builder.ensure(tool::Miri { compiler, target: self.host, extra_features: Vec::new() }); - let cargo_miri = builder.ensure(tool::CargoMiri { - compiler, - target: self.host, - extra_features: Vec::new(), - }); + let miri = builder + .ensure(tool::Miri { compiler, target: self.host, extra_features: Vec::new() }) + .expect("in-tree tool"); + let _cargo_miri = builder + .ensure(tool::CargoMiri { compiler, target: self.host, extra_features: Vec::new() }) + .expect("in-tree tool"); // The stdlib we need might be at a different stage. And just asking for the // sysroot does not seem to populate it, so we do that first. builder.ensure(compile::Std::new(compiler_std, host)); let sysroot = builder.sysroot(compiler_std); - if let (Some(miri), Some(_cargo_miri)) = (miri, cargo_miri) { - let mut cargo = - builder.cargo(compiler, Mode::ToolRustc, SourceType::Submodule, host, "install"); - cargo.arg("xargo"); - // Configure `cargo install` path. cargo adds a `bin/`. - cargo.env("CARGO_INSTALL_ROOT", &builder.out); - - let mut cargo = Command::from(cargo); - if !try_run(builder, &mut cargo) { - return; - } - - // # Run `cargo miri setup`. - let mut cargo = tool::prepare_tool_cargo( - builder, - compiler, - Mode::ToolRustc, - host, - "run", - "src/tools/miri/cargo-miri", - SourceType::Submodule, - &[], - ); - cargo.add_rustc_lib_path(builder, compiler); - cargo.arg("--").arg("miri").arg("setup"); - - // Tell `cargo miri setup` where to find the sources. - cargo.env("XARGO_RUST_SRC", builder.src.join("library")); - // Tell it where to find Miri. - cargo.env("MIRI", &miri); - // Debug things. - cargo.env("RUST_BACKTRACE", "1"); - // Let cargo-miri know where xargo ended up. - cargo.env("XARGO_CHECK", builder.out.join("bin").join("xargo-check")); - - let mut cargo = Command::from(cargo); - if !try_run(builder, &mut cargo) { - return; - } + let mut cargo = + builder.cargo(compiler, Mode::ToolRustc, SourceType::Submodule, host, "install"); + cargo.arg("xargo"); + // Configure `cargo install` path. cargo adds a `bin/`. + cargo.env("CARGO_INSTALL_ROOT", &builder.out); - // # Determine where Miri put its sysroot. - // To this end, we run `cargo miri setup --print-sysroot` and capture the output. - // (We do this separately from the above so that when the setup actually - // happens we get some output.) - // We re-use the `cargo` from above. - cargo.arg("--print-sysroot"); - - // FIXME: Is there a way in which we can re-use the usual `run` helpers? - let miri_sysroot = if builder.config.dry_run { - String::new() - } else { - builder.verbose(&format!("running: {:?}", cargo)); - let out = cargo - .output() - .expect("We already ran `cargo miri setup` before and that worked"); - assert!(out.status.success(), "`cargo miri setup` returned with non-0 exit code"); - // Output is "\n". - let stdout = String::from_utf8(out.stdout) - .expect("`cargo miri setup` stdout is not valid UTF-8"); - let sysroot = stdout.trim_end(); - builder.verbose(&format!("`cargo miri setup --print-sysroot` said: {:?}", sysroot)); - sysroot.to_owned() - }; - - // # Run `cargo test`. - let mut cargo = tool::prepare_tool_cargo( - builder, - compiler, - Mode::ToolRustc, - host, - "test", - "src/tools/miri", - SourceType::Submodule, - &[], - ); - cargo.add_rustc_lib_path(builder, compiler); - - // miri tests need to know about the stage sysroot - cargo.env("MIRI_SYSROOT", miri_sysroot); - cargo.env("MIRI_HOST_SYSROOT", sysroot); - cargo.env("RUSTC_LIB_PATH", builder.rustc_libdir(compiler)); - cargo.env("MIRI", miri); - // propagate --bless - if builder.config.cmd.bless() { - cargo.env("MIRI_BLESS", "Gesundheit"); - } + let mut cargo = Command::from(cargo); + if !try_run(builder, &mut cargo) { + return; + } - cargo.arg("--").args(builder.config.cmd.test_args()); + // # Run `cargo miri setup`. + let mut cargo = tool::prepare_tool_cargo( + builder, + compiler, + Mode::ToolRustc, + host, + "run", + "src/tools/miri/cargo-miri", + SourceType::Submodule, + &[], + ); + cargo.add_rustc_lib_path(builder, compiler); + cargo.arg("--").arg("miri").arg("setup"); + + // Tell `cargo miri setup` where to find the sources. + cargo.env("XARGO_RUST_SRC", builder.src.join("library")); + // Tell it where to find Miri. + cargo.env("MIRI", &miri); + // Debug things. + cargo.env("RUST_BACKTRACE", "1"); + // Let cargo-miri know where xargo ended up. + cargo.env("XARGO_CHECK", builder.out.join("bin").join("xargo-check")); + + let mut cargo = Command::from(cargo); + builder.run(&mut cargo); + + // # Determine where Miri put its sysroot. + // To this end, we run `cargo miri setup --print-sysroot` and capture the output. + // (We do this separately from the above so that when the setup actually + // happens we get some output.) + // We re-use the `cargo` from above. + cargo.arg("--print-sysroot"); + + // FIXME: Is there a way in which we can re-use the usual `run` helpers? + let miri_sysroot = if builder.config.dry_run { + String::new() + } else { + builder.verbose(&format!("running: {:?}", cargo)); + let out = + cargo.output().expect("We already ran `cargo miri setup` before and that worked"); + assert!(out.status.success(), "`cargo miri setup` returned with non-0 exit code"); + // Output is "\n". + let stdout = String::from_utf8(out.stdout) + .expect("`cargo miri setup` stdout is not valid UTF-8"); + let sysroot = stdout.trim_end(); + builder.verbose(&format!("`cargo miri setup --print-sysroot` said: {:?}", sysroot)); + sysroot.to_owned() + }; - let mut cargo = Command::from(cargo); - if !try_run(builder, &mut cargo) { - return; - } + // # Run `cargo test`. + let mut cargo = tool::prepare_tool_cargo( + builder, + compiler, + Mode::ToolRustc, + host, + "test", + "src/tools/miri", + SourceType::Submodule, + &[], + ); + cargo.add_rustc_lib_path(builder, compiler); - // # Done! - builder.save_toolstate("miri", ToolState::TestPass); - } else { - eprintln!("failed to test miri: could not build"); + // miri tests need to know about the stage sysroot + cargo.env("MIRI_SYSROOT", miri_sysroot); + cargo.env("MIRI_HOST_SYSROOT", sysroot); + cargo.env("RUSTC_LIB_PATH", builder.rustc_libdir(compiler)); + cargo.env("MIRI", miri); + // propagate --bless + if builder.config.cmd.bless() { + cargo.env("MIRI_BLESS", "Gesundheit"); } + + cargo.arg("--").args(builder.config.cmd.test_args()); + + let mut cargo = Command::from(cargo); + builder.run(&mut cargo); } } diff --git a/src/bootstrap/tool.rs b/src/bootstrap/tool.rs index 5d0c7d2bd9d44..ff6f7909a5a3b 100644 --- a/src/bootstrap/tool.rs +++ b/src/bootstrap/tool.rs @@ -868,8 +868,8 @@ tool_extended!((self, builder), Cargofmt, "src/tools/rustfmt", "cargo-fmt", stable=true, in_tree=true, {}; CargoClippy, "src/tools/clippy", "cargo-clippy", stable=true, in_tree=true, {}; Clippy, "src/tools/clippy", "clippy-driver", stable=true, in_tree=true, {}; - Miri, "src/tools/miri", "miri", stable=false, {}; - CargoMiri, "src/tools/miri/cargo-miri", "cargo-miri", stable=false, {}; + Miri, "src/tools/miri", "miri", stable=false, in_tree=true, {}; + CargoMiri, "src/tools/miri/cargo-miri", "cargo-miri", stable=false, in_tree=true, {}; Rls, "src/tools/rls", "rls", stable=true, {}; // FIXME: tool_std is not quite right, we shouldn't allow nightly features. // But `builder.cargo` doesn't know how to handle ToolBootstrap in stages other than 0, diff --git a/src/bootstrap/toolstate.rs b/src/bootstrap/toolstate.rs index f3a6759ab846b..1a17744322753 100644 --- a/src/bootstrap/toolstate.rs +++ b/src/bootstrap/toolstate.rs @@ -77,7 +77,6 @@ static STABLE_TOOLS: &[(&str, &str)] = &[ // though, as otherwise we will be unable to file an issue if they start // failing. static NIGHTLY_TOOLS: &[(&str, &str)] = &[ - ("miri", "src/tools/miri"), ("embedded-book", "src/doc/embedded-book"), // ("rustc-dev-guide", "src/doc/rustc-dev-guide"), ]; diff --git a/src/ci/docker/host-x86_64/x86_64-gnu-tools/checktools.sh b/src/ci/docker/host-x86_64/x86_64-gnu-tools/checktools.sh index 0fb8f41a7ec66..cf00c285b0a63 100755 --- a/src/ci/docker/host-x86_64/x86_64-gnu-tools/checktools.sh +++ b/src/ci/docker/host-x86_64/x86_64-gnu-tools/checktools.sh @@ -14,7 +14,6 @@ python3 "$X_PY" test --stage 2 --no-fail-fast \ src/doc/rust-by-example \ src/doc/embedded-book \ src/doc/edition-guide \ - src/tools/miri \ set -e @@ -23,3 +22,4 @@ cat /tmp/toolstate/toolstates.json python3 "$X_PY" test --stage 2 check-tools python3 "$X_PY" test --stage 2 src/tools/clippy python3 "$X_PY" test --stage 2 src/tools/rustfmt +python3 "$X_PY" test --stage 2 src/tools/miri diff --git a/src/tools/build-manifest/src/main.rs b/src/tools/build-manifest/src/main.rs index bf07cd75cab56..affc3e6db1967 100644 --- a/src/tools/build-manifest/src/main.rs +++ b/src/tools/build-manifest/src/main.rs @@ -11,9 +11,9 @@ mod versions; use crate::checksum::Checksums; use crate::manifest::{Component, Manifest, Package, Rename, Target}; use crate::versions::{PkgType, Versions}; -use std::collections::{BTreeMap, HashMap, HashSet}; +use std::collections::{BTreeMap, HashSet}; use std::env; -use std::fs::{self, File}; +use std::fs; use std::path::{Path, PathBuf}; static HOSTS: &[&str] = &[ @@ -239,7 +239,6 @@ fn main() { impl Builder { fn build(&mut self) { - self.check_toolstate(); let manifest = self.build_manifest(); let channel = self.versions.channel().to_string(); @@ -261,29 +260,6 @@ impl Builder { t!(self.checksums.store_cache()); } - /// If a tool does not pass its tests on *any* of Linux and Windows, don't ship - /// it on *all* targets, because tools like Miri can "cross-run" programs for - /// different targets, for example, run a program for `x86_64-pc-windows-msvc` - /// on `x86_64-unknown-linux-gnu`. - /// Right now, we do this only for Miri. - fn check_toolstate(&mut self) { - for file in &["toolstates-linux.json", "toolstates-windows.json"] { - let toolstates: Option> = File::open(self.input.join(file)) - .ok() - .and_then(|f| serde_json::from_reader(&f).ok()); - let toolstates = toolstates.unwrap_or_else(|| { - println!("WARNING: `{}` missing/malformed; assuming all tools failed", file); - HashMap::default() // Use empty map if anything went wrong. - }); - // Mark some tools as missing based on toolstate. - if toolstates.get("miri").map(|s| &*s as &str) != Some("test-pass") { - println!("Miri tests are not passing, removing component"); - self.versions.disable_version(&PkgType::Miri); - break; - } - } - } - fn build_manifest(&mut self) -> Manifest { let mut manifest = Manifest { manifest_version: "2".to_string(), diff --git a/src/tools/build-manifest/src/versions.rs b/src/tools/build-manifest/src/versions.rs index 95c2297de264b..92ef9968fe525 100644 --- a/src/tools/build-manifest/src/versions.rs +++ b/src/tools/build-manifest/src/versions.rs @@ -157,17 +157,6 @@ impl Versions { Ok(VersionInfo { version, git_commit, present: true }) } - pub(crate) fn disable_version(&mut self, package: &PkgType) { - match self.versions.get_mut(package) { - Some(version) => { - *version = VersionInfo::default(); - } - None => { - self.versions.insert(package.clone(), VersionInfo::default()); - } - } - } - pub(crate) fn archive_name( &self, package: &PkgType, diff --git a/src/tools/miri b/src/tools/miri deleted file mode 160000 index beed5eddb0f73..0000000000000 --- a/src/tools/miri +++ /dev/null @@ -1 +0,0 @@ -Subproject commit beed5eddb0f73f6721681560c73a51e3f15b8681 diff --git a/src/tools/miri/.editorconfig b/src/tools/miri/.editorconfig new file mode 100644 index 0000000000000..3c1f41bdcca6c --- /dev/null +++ b/src/tools/miri/.editorconfig @@ -0,0 +1,25 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + + +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 4 + +[*.rs] +indent_style = space +indent_size = 4 + +[*.toml] +indent_style = space +indent_size = 4 + +[*.md] +trim_trailing_whitespace = false diff --git a/src/tools/miri/.gitattributes b/src/tools/miri/.gitattributes new file mode 100644 index 0000000000000..2742e4d1d5b9b --- /dev/null +++ b/src/tools/miri/.gitattributes @@ -0,0 +1,6 @@ +* text=auto eol=lf + +# Older git versions try to fix line endings on images, this prevents it. +*.png binary +*.jpg binary +*.ico binary diff --git a/src/tools/miri/.github/workflows/ci.yml b/src/tools/miri/.github/workflows/ci.yml new file mode 100644 index 0000000000000..80d150e1df15d --- /dev/null +++ b/src/tools/miri/.github/workflows/ci.yml @@ -0,0 +1,156 @@ +name: CI + +on: + push: + # Run in PRs and for bors, but not on master. + branches: + - 'auto' + - 'try' + pull_request: + branches: + - 'master' + schedule: + - cron: '5 15 * * *' # At 15:05 UTC every day. + +jobs: + build: + runs-on: ${{ matrix.os }} + env: + RUST_BACKTRACE: 1 + HOST_TARGET: ${{ matrix.host_target }} + strategy: + matrix: + build: [linux64, macos, win32] + include: + - build: linux64 + os: ubuntu-latest + host_target: x86_64-unknown-linux-gnu + - build: macos + os: macos-latest + host_target: x86_64-apple-darwin + - build: win32 + os: windows-latest + host_target: i686-pc-windows-msvc + steps: + - uses: actions/checkout@v3 + + - name: Set the tag GC interval to 1 on linux + if: runner.os == 'Linux' + run: echo "MIRIFLAGS=-Zmiri-tag-gc=1" >> $GITHUB_ENV + + # We install gnu-tar because BSD tar is buggy on macOS builders of GHA. + # See . + - name: Install GNU tar + if: runner.os == 'macOS' + run: | + brew install gnu-tar + echo "/usr/local/opt/gnu-tar/libexec/gnubin" >> $GITHUB_PATH + + # Cache the global cargo directory, but NOT the local `target` directory which + # we cannot reuse anyway when the nightly changes (and it grows quite large + # over time). + - name: Add cache for cargo + id: cache + uses: actions/cache@v3 + with: + path: | + # Taken from . + ~/.cargo/bin + ~/.cargo/registry/index + ~/.cargo/registry/cache + ~/.cargo/git/db + # contains package information of crates installed via `cargo install`. + ~/.cargo/.crates.toml + ~/.cargo/.crates2.json + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock', 'cargo-miri/src/version.rs') }} + restore-keys: ${{ runner.os }}-cargo + + - name: Install rustup-toolchain-install-master and xargo + if: ${{ steps.cache.outputs.cache-hit == 'false' }} + shell: bash + run: | + cargo install rustup-toolchain-install-master + cargo install xargo + + - name: Install "master" toolchain + shell: bash + run: | + if [[ ${{ github.event_name }} == 'schedule' ]]; then + ./rustup-toolchain HEAD --host ${{ matrix.host_target }} + else + ./rustup-toolchain "" --host ${{ matrix.host_target }} + fi + + - name: Show Rust version + run: | + rustup show + rustc -Vv + cargo -V + + - name: Test + run: bash ./ci.sh + + style: + name: style checks + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Install required toolchain + # We need a toolchain that can actually build Miri, just a nightly won't do. + run: | + cargo install rustup-toolchain-install-master # TODO: cache this? + ./rustup-toolchain "" -c clippy + - name: rustfmt + run: ./miri fmt --check + - name: clippy + run: ./miri clippy -- -D warnings + - name: rustdoc + run: RUSTDOCFLAGS="-Dwarnings" cargo doc --document-private-items + + # These jobs doesn't actually test anything, but they're only used to tell + # bors the build completed, as there is no practical way to detect when a + # workflow is successful listening to webhooks only. + # + # ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB! + # (`fmt` is deliberately not listed, we want bors to ignore it.) + end-success: + name: bors build finished + runs-on: ubuntu-latest + needs: [build, style] + if: github.event.pusher.name == 'bors' && success() + steps: + - name: mark the job as a success + run: exit 0 + end-failure: + name: bors build finished + runs-on: ubuntu-latest + needs: [build, style] + if: github.event.pusher.name == 'bors' && (failure() || cancelled()) + steps: + - name: mark the job as a failure + run: exit 1 + + # Send a Zulip notification when a cron job fails + cron-fail-notify: + name: cronjob failure notification + runs-on: ubuntu-latest + needs: [build, style] + if: github.event_name == 'schedule' && (failure() || cancelled()) + steps: + - name: Install zulip-send + run: pip3 install zulip + - name: Send Zulip notification + shell: bash + env: + ZULIP_BOT_EMAIL: ${{ secrets.ZULIP_BOT_EMAIL }} + ZULIP_API_TOKEN: ${{ secrets.ZULIP_API_TOKEN }} + run: | + ~/.local/bin/zulip-send --stream miri --subject "Cron Job Failure (miri, $(date -u +%Y-%m))" \ + --message 'Dear @*T-miri*, + + It would appear that the Miri cron job build failed. Would you mind investigating this issue? + + Thanks in advance! + Sincerely, + The Miri Cronjobs Bot' \ + --user $ZULIP_BOT_EMAIL --api-key $ZULIP_API_TOKEN --site https://rust-lang.zulipchat.com diff --git a/src/tools/miri/.gitignore b/src/tools/miri/.gitignore new file mode 100644 index 0000000000000..924a93e807fe3 --- /dev/null +++ b/src/tools/miri/.gitignore @@ -0,0 +1,13 @@ +target +/doc +tex/*/out +*.dot +*.out +*.rs.bk +.vscode +*.mm_profdata +perf.data +perf.data.old +flamegraph.svg +tests/extern-so/libtestlib.so +.auto-* diff --git a/src/tools/miri/.gitpod.yml b/src/tools/miri/.gitpod.yml new file mode 100644 index 0000000000000..36bd991740a82 --- /dev/null +++ b/src/tools/miri/.gitpod.yml @@ -0,0 +1,9 @@ +image: ubuntu:latest + +tasks: + - before: echo "..." + init: | + cargo install rustup-toolchain-install-master + ./rustup-toolchain + ./miri build + command: echo "Run tests with ./miri test" \ No newline at end of file diff --git a/src/tools/miri/CONTRIBUTING.md b/src/tools/miri/CONTRIBUTING.md new file mode 100644 index 0000000000000..8d965ae8fcb8d --- /dev/null +++ b/src/tools/miri/CONTRIBUTING.md @@ -0,0 +1,277 @@ +# Contribution Guide + +If you want to hack on Miri yourself, great! Here are some resources you might +find useful. + +## Getting started + +Check out the issues on this GitHub repository for some ideas. In particular, +look for the green `E-*` labels which mark issues that should be rather +well-suited for onboarding. For more ideas or help with hacking on Miri, you can +contact us (`oli-obk` and `RalfJ`) on the [Rust Zulip]. + +[Rust Zulip]: https://rust-lang.zulipchat.com + +## Preparing the build environment + +Miri heavily relies on internal and unstable rustc interfaces to execute MIR, +which means it is important that you install a version of rustc that Miri +actually works with. + +The `rust-version` file contains the commit hash of rustc that Miri is currently +tested against. Other versions will likely not work. After installing +[`rustup-toolchain-install-master`], you can run the following command to +install that exact version of rustc as a toolchain: +``` +./rustup-toolchain +``` +This will set up a rustup toolchain called `miri` and set it as an override for +the current directory. + +You can also create a `.auto-everything` file (contents don't matter, can be empty), which +will cause any `./miri` command to automatically call `rustup-toolchain`, `clippy` and `rustfmt` +for you. If you don't want all of these to happen, you can add individual `.auto-toolchain`, +`.auto-clippy` and `.auto-fmt` files respectively. + +[`rustup-toolchain-install-master`]: https://github.com/kennytm/rustup-toolchain-install-master + +## Building and testing Miri + +Invoking Miri requires getting a bunch of flags right and setting up a custom +sysroot with xargo. The `miri` script takes care of that for you. With the +build environment prepared, compiling Miri is just one command away: + +``` +./miri build +``` + +Run `./miri` without arguments to see the other commands our build tool +supports. + +### Testing the Miri driver + +The Miri driver compiled from `src/bin/miri.rs` is the "heart" of Miri: it is +basically a version of `rustc` that, instead of compiling your code, runs it. +It accepts all the same flags as `rustc` (though the ones only affecting code +generation and linking obviously will have no effect) [and more][miri-flags]. + +[miri-flags]: README.md#miri--z-flags-and-environment-variables + +For example, you can (cross-)run the driver on a particular file by doing + +```sh +./miri run tests/pass/format.rs +./miri run tests/pass/hello.rs --target i686-unknown-linux-gnu +``` + +and you can (cross-)run the entire test suite using: + +``` +./miri test +MIRI_TEST_TARGET=i686-unknown-linux-gnu ./miri test +``` + +If your target doesn't support libstd, you can run miri with + +``` +MIRI_NO_STD=1 MIRI_TEST_TARGET=thumbv7em-none-eabihf ./miri test tests/fail/alloc/no_global_allocator.rs +MIRI_NO_STD=1 ./miri run tests/pass/no_std.rs --target thumbv7em-none-eabihf +``` + +to avoid attempting (and failing) to build libstd. Note that almost no tests will pass +this way, but you can run individual tests. + +`./miri test FILTER` only runs those tests that contain `FILTER` in their +filename (including the base directory, e.g. `./miri test fail` will run all +compile-fail tests). + +You can get a trace of which MIR statements are being executed by setting the +`MIRI_LOG` environment variable. For example: + +```sh +MIRI_LOG=info ./miri run tests/pass/vec.rs +``` + +Setting `MIRI_LOG` like this will configure logging for Miri itself as well as +the `rustc_middle::mir::interpret` and `rustc_mir::interpret` modules in rustc. You +can also do more targeted configuration, e.g. the following helps debug the +stacked borrows implementation: + +```sh +MIRI_LOG=rustc_mir::interpret=info,miri::stacked_borrows ./miri run tests/pass/vec.rs +``` + +In addition, you can set `MIRI_BACKTRACE=1` to get a backtrace of where an +evaluation error was originally raised. + +#### UI testing + +We use ui-testing in Miri, meaning we generate `.stderr` and `.stdout` files for the output +produced by Miri. You can use `./miri bless` to automatically (re)generate these files when +you add new tests or change how Miri presents certain output. + +Note that when you also use `MIRIFLAGS` to change optimizations and similar, the ui output +will change in unexpected ways. In order to still be able +to run the other checks while ignoring the ui output, use `MIRI_SKIP_UI_CHECKS=1 ./miri test`. + +For more info on how to configure ui tests see [the documentation on the ui test crate][ui_test] + +[ui_test]: ui_test/README.md + +### Testing `cargo miri` + +Working with the driver directly gives you full control, but you also lose all +the convenience provided by cargo. Once your test case depends on a crate, it +is probably easier to test it with the cargo wrapper. You can install your +development version of Miri using + +``` +./miri install +``` + +and then you can use it as if it was installed by `rustup`. Make sure you use +the same toolchain when calling `cargo miri` that you used when installing Miri! +Usually this means you have to write `cargo +miri miri ...` to select the `miri` +toolchain that was installed by `./rustup-toolchain`. + +There's a test for the cargo wrapper in the `test-cargo-miri` directory; run +`./run-test.py` in there to execute it. Like `./miri test`, this respects the +`MIRI_TEST_TARGET` environment variable to execute the test for another target. + +### Using a modified standard library + +Miri re-builds the standard library into a custom sysroot, so it is fairly easy +to test Miri against a modified standard library -- you do not even have to +build Miri yourself, the Miri shipped by `rustup` will work. All you have to do +is set the `MIRI_LIB_SRC` environment variable to the `library` folder of a +`rust-lang/rust` repository checkout. Note that changing files in that directory +does not automatically trigger a re-build of the standard library; you have to +clear the Miri build cache manually (on Linux, `rm -rf ~/.cache/miri`; +and on Windows, `rmdir /S "%LOCALAPPDATA%\rust-lang\miri\cache"`). + +### Benchmarking + +Miri comes with a few benchmarks; you can run `./miri bench` to run them with the locally built +Miri. Note: this will run `./miri install` as a side-effect. Also requires `hyperfine` to be +installed (`cargo install hyperfine`). + +## Configuring `rust-analyzer` + +To configure `rust-analyzer` and VS Code for working on Miri, save the following +to `.vscode/settings.json` in your local Miri clone: + +```json +{ + "rust-analyzer.rustc.source": "discover", + "rust-analyzer.linkedProjects": [ + "./Cargo.toml", + "./cargo-miri/Cargo.toml" + ], + "rust-analyzer.checkOnSave.overrideCommand": [ + "env", + "MIRI_AUTO_OPS=no", + "./miri", + "cargo", + "clippy", // make this `check` when working with a locally built rustc + "--message-format=json" + ], + // Contrary to what the name suggests, this also affects proc macros. + "rust-analyzer.cargo.buildScripts.overrideCommand": [ + "env", + "MIRI_AUTO_OPS=no", + "./miri", + "cargo", + "check", + "--message-format=json", + ], +} +``` + +> #### Note +> +> If you are [building Miri with a locally built rustc][], set +> `rust-analyzer.rustcSource` to the relative path from your Miri clone to the +> root `Cargo.toml` of the locally built rustc. For example, the path might look +> like `../rust/Cargo.toml`. + +See the rustc-dev-guide's docs on ["Configuring `rust-analyzer` for `rustc`"][rdg-r-a] +for more information about configuring VS Code and `rust-analyzer`. + +[rdg-r-a]: https://rustc-dev-guide.rust-lang.org/building/suggested.html#configuring-rust-analyzer-for-rustc + +## Advanced topic: other build environments + +We described above the simplest way to get a working build environment for Miri, +which is to use the version of rustc indicated by `rustc-version`. But +sometimes, that is not enough. + +### Updating `rustc-version` + +The `rustc-version` file is regularly updated to keep Miri close to the latest +version of rustc. Usually, new contributors do not have to worry about this. But +sometimes a newer rustc is needed for a patch, and sometimes Miri needs fixing +for changes in rustc. In both cases, `rustc-version` needs updating. + +To update the `rustc-version` file and install the latest rustc, you can run: +``` +./rustup-toolchain HEAD +``` + +Now edit Miri until `./miri test` passes, and submit a PR. Generally, it is +preferred to separate updating `rustc-version` and doing what it takes to get +Miri working again, from implementing new features that rely on the updated +rustc. This avoids blocking all Miri development on landing a big PR. + +### Building Miri with a locally built rustc + +[building Miri with a locally built rustc]: #building-miri-with-a-locally-built-rustc + +A big part of the Miri driver lives in rustc, so working on Miri will sometimes +require using a locally built rustc. The bug you want to fix may actually be on +the rustc side, or you just need to get more detailed trace of the execution +than what is possible with release builds -- in both cases, you should develop +Miri against a rustc you compiled yourself, with debug assertions (and hence +tracing) enabled. + +The setup for a local rustc works as follows: +```sh +# Clone the rust-lang/rust repo. +git clone https://github.com/rust-lang/rust rustc +cd rustc +# Create a config.toml with defaults for working on Miri. +./x.py setup compiler + # Now edit `config.toml` and under `[rust]` set `debug-assertions = true`. + +# Build a stage 2 rustc, and build the rustc libraries with that rustc. +# This step can take 30 minutes or more. +./x.py build --stage 2 compiler/rustc +# If you change something, you can get a faster rebuild by doing +./x.py build --keep-stage 0 --stage 2 compiler/rustc +# You may have to change the architecture in the next command +rustup toolchain link stage2 build/x86_64-unknown-linux-gnu/stage2 +# Now cd to your Miri directory, then configure rustup +rustup override set stage2 +``` + +Note: When you are working with a locally built rustc or any other toolchain that +is not the same as the one in `rust-version`, you should not have `.auto-everything` or +`.auto-toolchain` as that will keep resetting your toolchain. + +``` +rm -f .auto-everything .auto-toolchain +``` + +Important: You need to delete the Miri cache when you change the stdlib; otherwise the +old, chached version will be used. On Linux, the cache is located at `~/.cache/miri`, +and on Windows, it is located at `%LOCALAPPDATA%\rust-lang\miri\cache`; the exact +location is printed after the library build: "A libstd for Miri is now available in ...". + +Note: `./x.py --stage 2 compiler/rustc` currently errors with `thread 'main' +panicked at 'fs::read(stamp) failed with No such file or directory (os error 2)`, +you can simply ignore that error; Miri will build anyway. + +For more information about building and configuring a local compiler, +see . + +With this, you should now have a working development setup! See +[above](#building-and-testing-miri) for how to proceed working on Miri. diff --git a/src/tools/miri/Cargo.lock b/src/tools/miri/Cargo.lock new file mode 100644 index 0000000000000..9df35ec0deb2e --- /dev/null +++ b/src/tools/miri/Cargo.lock @@ -0,0 +1,813 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11a17d453482a265fd5f8479f2a3f405566e6ca627837aaddb85af8b1ab8ef61" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "camino" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "869119e97797867fd90f5e22af7d0bd274bd4635ebb9eb68c04f3f513ae6c412" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3abb7553d5b9b8421c6de7cb02606ff15e0c6eea7d8eadd75ef013fd636bec36" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", +] + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "color-eyre" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ebf286c900a6d5867aeff75cfee3192857bb7f24b547d4f0df2ed6baa812c90" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", +] + +[[package]] +name = "color-spantrace" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba75b3d9449ecdccb27ecbc479fdc0b87fa2dd43d2f8298f9bf0e59aacc8dce" +dependencies = [ + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", +] + +[[package]] +name = "colored" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" +dependencies = [ + "atty", + "lazy_static", + "winapi", +] + +[[package]] +name = "crossbeam" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae5588f6b3c3cb05239e90bd110f257254aecd01e4635400391aeae07497845" +dependencies = [ + "cfg-if", + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "lazy_static", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f25d8400f4a7a5778f0e4e52384a48cbd9b5c495d110786187fc750075277a2" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" +dependencies = [ + "cfg-if", + "lazy_static", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "env_logger" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "eyre" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "getrandom" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "itoa" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.112" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" + +[[package]] +name = "libffi" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e454b3efb16fba3b17810ae5e41df02b649e564ab3c5a34b3b93ed07ad287e6" +dependencies = [ + "libc", + "libffi-sys", +] + +[[package]] +name = "libffi-sys" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab4106b7f09d7b87d021334d5618fac1dfcfb824d4c5fe111ff0074dfd242e15" +dependencies = [ + "cc", +] + +[[package]] +name = "libloading" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "lock_api" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "measureme" +version = "10.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbdc226fa10994e8f66a4d2f6f000148bc563a1c671b6dcd2135737018033d8a" +dependencies = [ + "log", + "memmap2", + "parking_lot", + "perf-event-open-sys", + "rustc-hash", + "smallvec", +] + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "memmap2" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "723e3ebdcdc5c023db1df315364573789f8857c11b631a2fdfad7c00f5c046b4" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miniz_oxide" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" +dependencies = [ + "adler", +] + +[[package]] +name = "miri" +version = "0.1.0" +dependencies = [ + "colored", + "env_logger", + "getrandom", + "lazy_static", + "libc", + "libffi", + "libloading", + "log", + "measureme", + "rand", + "regex", + "rustc-workspace-hack", + "shell-escape", + "smallvec", + "ui_test", +] + +[[package]] +name = "object" +version = "0.28.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" + +[[package]] +name = "owo-colors" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "decf7381921fea4dcb2549c5667eda59b3ec297ab7e2b5fc33eac69d2e7da87b" + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[package]] +name = "perf-event-open-sys" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce9bedf5da2c234fdf2391ede2b90fabf585355f33100689bc364a3ea558561a" +dependencies = [ + "libc", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "ppv-lite86" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" + +[[package]] +name = "proc-macro2" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" +dependencies = [ + "rand_core", +] + +[[package]] +name = "redox_syscall" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-workspace-hack" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc71d2faa173b74b232dedc235e3ee1696581bb132fc116fa3626d6151a1a8fb" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "ryu" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "semver" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cb243bdfdb5936c8dc3c45762a19d12ab4550cdc753bc247637d4ec35a040fd" +dependencies = [ + "serde", +] + +[[package]] +name = "serde" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shell-escape" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f" + +[[package]] +name = "smallvec" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" + +[[package]] +name = "syn" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tracing" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-error" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" +dependencies = [ + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a713421342a5a666b7577783721d3117f1b69a393df803ee17bb73b1e122a59" +dependencies = [ + "sharded-slab", + "thread_local", + "tracing-core", +] + +[[package]] +name = "ui_test" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d1f546a5883ae78da735bba529ec1116661e2f73582f23920d994dc97da3a22" +dependencies = [ + "cargo_metadata", + "color-eyre", + "colored", + "crossbeam", + "diff", + "lazy_static", + "regex", + "rustc_version", + "serde", + "serde_json", +] + +[[package]] +name = "unicode-ident" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/src/tools/miri/Cargo.toml b/src/tools/miri/Cargo.toml new file mode 100644 index 0000000000000..0c547d585d198 --- /dev/null +++ b/src/tools/miri/Cargo.toml @@ -0,0 +1,61 @@ +[package] +authors = ["Miri Team"] +description = "An experimental interpreter for Rust MIR (core driver)." +license = "MIT OR Apache-2.0" +name = "miri" +repository = "https://github.com/rust-lang/miri" +version = "0.1.0" +default-run = "miri" +edition = "2021" + +[lib] +test = true # we have unit tests +doctest = false # but no doc tests + +[[bin]] +name = "miri" +test = false # we have no unit tests +doctest = false # and no doc tests + +[dependencies] +getrandom = { version = "0.2", features = ["std"] } +env_logger = "0.9" +log = "0.4" +shell-escape = "0.1.4" +rand = "0.8" +smallvec = "1.7" + +# A noop dependency that changes in the Rust repository, it's a bit of a hack. +# See the `src/tools/rustc-workspace-hack/README.md` file in `rust-lang/rust` +# for more information. +rustc-workspace-hack = "1.0.0" +measureme = "10.0.0" + +[target."cfg(unix)".dependencies] +libc = "0.2" +libffi = "3.0.0" +libloading = "0.7" + +[dev-dependencies] +colored = "2" +ui_test = "0.3.1" +# Features chosen to match those required by env_logger, to avoid rebuilds +regex = { version = "1.5.5", default-features = false, features = ["perf", "std"] } +lazy_static = "1.4.0" + +[package.metadata.rust-analyzer] +# This crate uses #[feature(rustc_private)]. +# See https://github.com/rust-analyzer/rust-analyzer/pull/7891 +rustc_private = true + +[[test]] +name = "compiletest" +harness = false + +[features] +default = ["stack-cache"] +stack-cache = [] + +# Be aware that this file is inside a workspace when used via the +# submodule in the rustc repo. That means there are many cargo features +# we cannot use, such as profiles. diff --git a/src/tools/miri/LICENSE-APACHE b/src/tools/miri/LICENSE-APACHE new file mode 100644 index 0000000000000..1b5ec8b78e237 --- /dev/null +++ b/src/tools/miri/LICENSE-APACHE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS diff --git a/src/tools/miri/LICENSE-MIT b/src/tools/miri/LICENSE-MIT new file mode 100644 index 0000000000000..31aa79387f27e --- /dev/null +++ b/src/tools/miri/LICENSE-MIT @@ -0,0 +1,23 @@ +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/src/tools/miri/README.md b/src/tools/miri/README.md new file mode 100644 index 0000000000000..1f4c52d5b8507 --- /dev/null +++ b/src/tools/miri/README.md @@ -0,0 +1,639 @@ +# Miri + +[![Actions build status][actions-badge]][actions-url] + +[actions-badge]: https://github.com/rust-lang/miri/workflows/CI/badge.svg?branch=master +[actions-url]: https://github.com/rust-lang/miri/actions + +An experimental interpreter for [Rust][rust]'s +[mid-level intermediate representation][mir] (MIR). It can run binaries and +test suites of cargo projects and detect certain classes of +[undefined behavior](https://doc.rust-lang.org/reference/behavior-considered-undefined.html), +for example: + +* Out-of-bounds memory accesses and use-after-free +* Invalid use of uninitialized data +* Violation of intrinsic preconditions (an [`unreachable_unchecked`] being + reached, calling [`copy_nonoverlapping`] with overlapping ranges, ...) +* Not sufficiently aligned memory accesses and references +* Violation of *some* basic type invariants (a `bool` that is not 0 or 1, for example, + or an invalid enum discriminant) +* **Experimental**: Violations of the [Stacked Borrows] rules governing aliasing + for reference types +* **Experimental**: Data races + +On top of that, Miri will also tell you about memory leaks: when there is memory +still allocated at the end of the execution, and that memory is not reachable +from a global `static`, Miri will raise an error. + +Miri supports almost all Rust language features; in particular, unwinding and +concurrency are properly supported (including some experimental emulation of +weak memory effects, i.e., reads can return outdated values). + +You can use Miri to emulate programs on other targets, e.g. to ensure that +byte-level data manipulation works correctly both on little-endian and +big-endian systems. See +[cross-interpretation](#cross-interpretation-running-for-different-targets) +below. + +Miri has already discovered some [real-world bugs](#bugs-found-by-miri). If you +found a bug with Miri, we'd appreciate if you tell us and we'll add it to the +list! + +By default, Miri ensures a fully deterministic execution and isolates the +program from the host system. Some APIs that would usually access the host, such +as gathering entropy for random number generators, environment variables, and +clocks, are replaced by deterministic "fake" implementations. Set +`MIRIFLAGS="-Zmiri-disable-isolation"` to access the real system APIs instead. +(In particular, the "fake" system RNG APIs make Miri **not suited for +cryptographic use**! Do not generate keys using Miri.) + +All that said, be aware that Miri will **not catch all cases of undefined +behavior** in your program, and cannot run all programs: + +* There are still plenty of open questions around the basic invariants for some + types and when these invariants even have to hold. Miri tries to avoid false + positives here, so if your program runs fine in Miri right now that is by no + means a guarantee that it is UB-free when these questions get answered. + + In particular, Miri does currently not check that references point to valid data. +* If the program relies on unspecified details of how data is laid out, it will + still run fine in Miri -- but might break (including causing UB) on different + compiler versions or different platforms. +* Program execution is non-deterministic when it depends, for example, on where + exactly in memory allocations end up, or on the exact interleaving of + concurrent threads. Miri tests one of many possible executions of your + program. You can alleviate this to some extent by running Miri with different + values for `-Zmiri-seed`, but that will still by far not explore all possible + executions. +* Miri runs the program as a platform-independent interpreter, so the program + has no access to most platform-specific APIs or FFI. A few APIs have been + implemented (such as printing to stdout, accessing environment variables, and + basic file system access) but most have not: for example, Miri currently does + not support networking. System API support varies between targets; if you run + on Windows it is a good idea to use `--target x86_64-unknown-linux-gnu` to get + better support. +* Weak memory emulation may [produce weak behaviours](https://github.com/rust-lang/miri/issues/2301) + unobservable by compiled programs running on real hardware when `SeqCst` fences are used, and it + cannot produce all behaviors possibly observable on real hardware. + +[rust]: https://www.rust-lang.org/ +[mir]: https://github.com/rust-lang/rfcs/blob/master/text/1211-mir.md +[`unreachable_unchecked`]: https://doc.rust-lang.org/stable/std/hint/fn.unreachable_unchecked.html +[`copy_nonoverlapping`]: https://doc.rust-lang.org/stable/std/ptr/fn.copy_nonoverlapping.html +[Stacked Borrows]: https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md + + +## Using Miri + +Install Miri on Rust nightly via `rustup`: + +```sh +rustup +nightly component add miri +``` + +If `rustup` says the `miri` component is unavailable, that's because not all +nightly releases come with all tools. Check out +[this website](https://rust-lang.github.io/rustup-components-history) to +determine a nightly version that comes with Miri and install that using `rustup +toolchain install nightly-YYYY-MM-DD`. Either way, all of the following commands +assume the right toolchain is pinned via `rustup override set nightly` or +`rustup override set nightly-YYYY-MM-DD`. (Alternatively, use `cargo ++nightly`/`cargo +nightly-YYYY-MM-DD` for each of the following commands.) + +Now you can run your project in Miri: + +1. Run `cargo clean` to eliminate any cached dependencies. Miri needs your + dependencies to be compiled the right way, that would not happen if they have + previously already been compiled. +2. To run all tests in your project through Miri, use `cargo miri test`. +3. If you have a binary project, you can run it through Miri using `cargo miri run`. + +The first time you run Miri, it will perform some extra setup and install some +dependencies. It will ask you for confirmation before installing anything. + +`cargo miri run/test` supports the exact same flags as `cargo run/test`. For +example, `cargo miri test filter` only runs the tests containing `filter` in +their name. + +You can pass arguments to Miri via `MIRIFLAGS`. For example, +`MIRIFLAGS="-Zmiri-disable-stacked-borrows" cargo miri run` runs the program +without checking the aliasing of references. + +When compiling code via `cargo miri`, the `cfg(miri)` config flag is set for code +that will be interpret under Miri. You can use this to ignore test cases that fail +under Miri because they do things Miri does not support: + +```rust +#[test] +#[cfg_attr(miri, ignore)] +fn does_not_work_on_miri() { + tokio::run(futures::future::ok::<_, ()>(())); +} +``` + +There is no way to list all the infinite things Miri cannot do, but the +interpreter will explicitly tell you when it finds something unsupported: + +``` +error: unsupported operation: can't call foreign function: bind + ... + = help: this is likely not a bug in the program; it indicates that the program \ + performed an operation that the interpreter does not support +``` + +### Cross-interpretation: running for different targets + +Miri can not only run a binary or test suite for your host target, it can also +perform cross-interpretation for arbitrary foreign targets: `cargo miri run +--target x86_64-unknown-linux-gnu` will run your program as if it was a Linux +program, no matter your host OS. This is particularly useful if you are using +Windows, as the Linux target is much better supported than Windows targets. + +You can also use this to test platforms with different properties than your host +platform. For example `cargo miri test --target mips64-unknown-linux-gnuabi64` +will run your test suite on a big-endian target, which is useful for testing +endian-sensitive code. + +### Running Miri on CI + +To run Miri on CI, make sure that you handle the case where the latest nightly +does not ship the Miri component because it currently does not build. `rustup +toolchain install --component` knows how to handle this situation, so the +following snippet should always work: + +```sh +rustup toolchain install nightly --component miri +rustup override set nightly + +cargo miri test +``` + +Here is an example job for GitHub Actions: + +```yaml + miri: + name: "Miri" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Install Miri + run: | + rustup toolchain install nightly --component miri + rustup override set nightly + cargo miri setup + - name: Test with Miri + run: cargo miri test +``` + +The explicit `cargo miri setup` helps to keep the output of the actual test step +clean. + +### Testing for alignment issues + +Miri can sometimes miss misaligned accesses since allocations can "happen to be" +aligned just right. You can use `-Zmiri-symbolic-alignment-check` to definitely +catch all such issues, but that flag will also cause false positives when code +does manual pointer arithmetic to account for alignment. Another alternative is +to call Miri with various values for `-Zmiri-seed`; that will alter the +randomness that is used to determine allocation base addresses. The following +snippet calls Miri in a loop with different values for the seed: + +``` +for SEED in $({ echo obase=16; seq 0 255; } | bc); do + echo "Trying seed: $SEED" + MIRIFLAGS=-Zmiri-seed=$SEED cargo miri test || { echo "Failing seed: $SEED"; break; }; +done +``` + +### Supported targets + +Miri does not support all targets supported by Rust. The good news, however, is +that no matter your host OS/platform, it is easy to run code for *any* target +using `--target`! + +The following targets are tested on CI and thus should always work (to the +degree documented below): + +- The best-supported target is `x86_64-unknown-linux-gnu`. Miri releases are + blocked on things working with this target. Most other Linux targets should + also work well; we do run the test suite on `i686-unknown-linux-gnu` as a + 32bit target and `mips64-unknown-linux-gnuabi64` as a big-endian target. +- `x86_64-apple-darwin` should work basically as well as Linux. We also test + `aarch64-apple-darwin`. However, we might ship Miri with a nightly even when + some features on these targets regress. +- `x86_64-pc-windows-msvc` works, but supports fewer features than the Linux and + Apple targets. For example, file system access and concurrency are not + supported on Windows. We also test `i686-pc-windows-msvc`, with the same + reduced feature set. We might ship Miri with a nightly even when some features + on these targets regress. + +### Common Problems + +When using the above instructions, you may encounter a number of confusing compiler +errors. + +#### "note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace" + +You may see this when trying to get Miri to display a backtrace. By default, Miri +doesn't expose any environment to the program, so running +`RUST_BACKTRACE=1 cargo miri test` will not do what you expect. + +To get a backtrace, you need to disable isolation +[using `-Zmiri-disable-isolation`][miri-flags]: + +```sh +RUST_BACKTRACE=1 MIRIFLAGS="-Zmiri-disable-isolation" cargo miri test +``` + +#### "found possibly newer version of crate `std` which `` depends on" + +Your build directory may contain artifacts from an earlier build that have/have +not been built for Miri. Run `cargo clean` before switching from non-Miri to +Miri builds and vice-versa. + +#### "found crate `std` compiled by an incompatible version of rustc" + +You may be running `cargo miri` with a different compiler version than the one +used to build the custom libstd that Miri uses, and Miri failed to detect that. +Try deleting `~/.cache/miri`. + +#### "no mir for `std::rt::lang_start_internal`" + +This means the sysroot you are using was not compiled with Miri in mind. This +should never happen when you use `cargo miri` because that takes care of setting +up the sysroot. If you are using `miri` (the Miri driver) directly, see the +[contributors' guide](CONTRIBUTING.md) for how to use `./miri` to best do that. + + +## Miri `-Z` flags and environment variables +[miri-flags]: #miri--z-flags-and-environment-variables + +Miri adds its own set of `-Z` flags, which are usually set via the `MIRIFLAGS` +environment variable. We first document the most relevant and most commonly used flags: + +* `-Zmiri-compare-exchange-weak-failure-rate=` changes the failure rate of + `compare_exchange_weak` operations. The default is `0.8` (so 4 out of 5 weak ops will fail). + You can change it to any value between `0.0` and `1.0`, where `1.0` means it + will always fail and `0.0` means it will never fail. Note than setting it to + `1.0` will likely cause hangs, since it means programs using + `compare_exchange_weak` cannot make progress. +* `-Zmiri-disable-isolation` disables host isolation. As a consequence, + the program has access to host resources such as environment variables, file + systems, and randomness. +* `-Zmiri-isolation-error=` configures Miri's response to operations + requiring host access while isolation is enabled. `abort`, `hide`, `warn`, + and `warn-nobacktrace` are the supported actions. The default is to `abort`, + which halts the machine. Some (but not all) operations also support continuing + execution with a "permission denied" error being returned to the program. + `warn` prints a full backtrace when that happens; `warn-nobacktrace` is less + verbose. `hide` hides the warning entirely. +* `-Zmiri-env-forward=` forwards the `var` environment variable to the interpreted program. Can + be used multiple times to forward several variables. Execution will still be deterministic if the + value of forwarded variables stays the same. Has no effect if `-Zmiri-disable-isolation` is set. +* `-Zmiri-ignore-leaks` disables the memory leak checker, and also allows some + remaining threads to exist when the main thread exits. +* `-Zmiri-permissive-provenance` disables the warning for integer-to-pointer casts and + [`ptr::from_exposed_addr`](https://doc.rust-lang.org/nightly/std/ptr/fn.from_exposed_addr.html). + This will necessarily miss some bugs as those operations are not efficiently and accurately + implementable in a sanitizer, but it will only miss bugs that concern memory/pointers which is + subject to these operations. +* `-Zmiri-preemption-rate` configures the probability that at the end of a basic block, the active + thread will be preempted. The default is `0.01` (i.e., 1%). Setting this to `0` disables + preemption. +* `-Zmiri-report-progress` makes Miri print the current stacktrace every now and then, so you can + tell what it is doing when a program just keeps running. You can customize how frequently the + report is printed via `-Zmiri-report-progress=`, which prints the report every N basic + blocks. +* `-Zmiri-seed=` configures the seed of the RNG that Miri uses to resolve non-determinism. This + RNG is used to pick base addresses for allocations, to determine preemption and failure of + `compare_exchange_weak`, and to control store buffering for weak memory emulation. When isolation + is enabled (the default), this is also used to emulate system entropy. The default seed is 0. You + can increase test coverage by running Miri multiple times with different seeds. +* `-Zmiri-strict-provenance` enables [strict + provenance](https://github.com/rust-lang/rust/issues/95228) checking in Miri. This means that + casting an integer to a pointer yields a result with 'invalid' provenance, i.e., with provenance + that cannot be used for any memory access. +* `-Zmiri-symbolic-alignment-check` makes the alignment check more strict. By default, alignment is + checked by casting the pointer to an integer, and making sure that is a multiple of the alignment. + This can lead to cases where a program passes the alignment check by pure chance, because things + "happened to be" sufficiently aligned -- there is no UB in this execution but there would be UB in + others. To avoid such cases, the symbolic alignment check only takes into account the requested + alignment of the relevant allocation, and the offset into that allocation. This avoids missing + such bugs, but it also incurs some false positives when the code does manual integer arithmetic to + ensure alignment. (The standard library `align_to` method works fine in both modes; under + symbolic alignment it only fills the middle slice when the allocation guarantees sufficient + alignment.) +* `-Zmiri-tag-gc=` configures how often the pointer tag garbage collector runs. The default + is to search for and remove unreachable tags once every `10,000` basic blocks. Setting this to + `0` disables the garbage collector, which causes some programs to have explosive memory usage + and/or super-linear runtime. + +The remaining flags are for advanced use only, and more likely to change or be removed. +Some of these are **unsound**, which means they can lead +to Miri failing to detect cases of undefined behavior in a program. + +* `-Zmiri-disable-abi-check` disables checking [function ABI]. Using this flag + is **unsound**. +* `-Zmiri-disable-alignment-check` disables checking pointer alignment, so you + can focus on other failures, but it means Miri can miss bugs in your program. + Using this flag is **unsound**. +* `-Zmiri-disable-data-race-detector` disables checking for data races. Using + this flag is **unsound**. This implies `-Zmiri-disable-weak-memory-emulation`. +* `-Zmiri-disable-stacked-borrows` disables checking the experimental + [Stacked Borrows] aliasing rules. This can make Miri run faster, but it also + means no aliasing violations will be detected. Using this flag is **unsound** + (but the affected soundness rules are experimental). +* `-Zmiri-disable-validation` disables enforcing validity invariants, which are + enforced by default. This is mostly useful to focus on other failures (such + as out-of-bounds accesses) first. Setting this flag means Miri can miss bugs + in your program. However, this can also help to make Miri run faster. Using + this flag is **unsound**. +* `-Zmiri-disable-weak-memory-emulation` disables the emulation of some C++11 weak + memory effects. +* `-Zmiri-extern-so-file=` is an experimental flag for providing support + for FFI calls. Functions not provided by that file are still executed via the usual Miri shims. + **WARNING**: If an invalid/incorrect `.so` file is specified, this can cause undefined behaviour in Miri itself! + And of course, Miri cannot do any checks on the actions taken by the external code. + Note that Miri has its own handling of file descriptors, so if you want to replace *some* functions + working on file descriptors, you will have to replace *all* of them, or the two kinds of + file descriptors will be mixed up. + This is **work in progress**; currently, only integer arguments and return values are + supported (and no, pointer/integer casts to work around this limitation will not work; + they will fail horribly). It also only works on unix hosts for now. + Follow [the discussion on supporting other types](https://github.com/rust-lang/miri/issues/2365). +* `-Zmiri-measureme=` enables `measureme` profiling for the interpreted program. + This can be used to find which parts of your program are executing slowly under Miri. + The profile is written out to a file with the prefix ``, and can be processed + using the tools in the repository https://github.com/rust-lang/measureme. +* `-Zmiri-mute-stdout-stderr` silently ignores all writes to stdout and stderr, + but reports to the program that it did actually write. This is useful when you + are not interested in the actual program's output, but only want to see Miri's + errors and warnings. +* `-Zmiri-panic-on-unsupported` will makes some forms of unsupported functionality, + such as FFI and unsupported syscalls, panic within the context of the emulated + application instead of raising an error within the context of Miri (and halting + execution). Note that code might not expect these operations to ever panic, so + this flag can lead to strange (mis)behavior. +* `-Zmiri-retag-fields` changes Stacked Borrows retagging to recurse into fields. + This means that references in fields of structs/enums/tuples/arrays/... are retagged, + and in particular, they are protected when passed as function arguments. +* `-Zmiri-track-alloc-id=,,...` shows a backtrace when the given allocations are + being allocated or freed. This helps in debugging memory leaks and + use after free bugs. Specifying this argument multiple times does not overwrite the previous + values, instead it appends its values to the list. Listing an id multiple times has no effect. +* `-Zmiri-track-call-id=,,...` shows a backtrace when the given call ids are + assigned to a stack frame. This helps in debugging UB related to Stacked + Borrows "protectors". Specifying this argument multiple times does not overwrite the previous + values, instead it appends its values to the list. Listing an id multiple times has no effect. +* `-Zmiri-track-pointer-tag=,,...` shows a backtrace when a given pointer tag + is created and when (if ever) it is popped from a borrow stack (which is where the tag becomes invalid + and any future use of it will error). This helps you in finding out why UB is + happening and where in your code would be a good place to look for it. + Specifying this argument multiple times does not overwrite the previous + values, instead it appends its values to the list. Listing a tag multiple times has no effect. +* `-Zmiri-track-weak-memory-loads` shows a backtrace when weak memory emulation returns an outdated + value from a load. This can help diagnose problems that disappear under + `-Zmiri-disable-weak-memory-emulation`. + +[function ABI]: https://doc.rust-lang.org/reference/items/functions.html#extern-function-qualifier + +Some native rustc `-Z` flags are also very relevant for Miri: + +* `-Zmir-opt-level` controls how many MIR optimizations are performed. Miri + overrides the default to be `0`; be advised that using any higher level can + make Miri miss bugs in your program because they got optimized away. +* `-Zalways-encode-mir` makes rustc dump MIR even for completely monomorphic + functions. This is needed so that Miri can execute such functions, so Miri + sets this flag per default. +* `-Zmir-emit-retag` controls whether `Retag` statements are emitted. Miri + enables this per default because it is needed for [Stacked Borrows]. + +Moreover, Miri recognizes some environment variables: + +* `MIRI_AUTO_OPS` indicates whether the automatic execution of rustfmt, clippy and rustup-toolchain + should be skipped. If it is set to any value, they are skipped. This is used for avoiding + infinite recursion in `./miri` and 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. Note that changing files in that directory does not automatically + trigger a re-build of the standard library; you have to clear the Miri build + cache manually (on Linux, `rm -rf ~/.cache/miri`). +* `MIRI_SYSROOT` (recognized by `cargo miri` and the Miri driver) indicates the sysroot to use. When + using `cargo miri`, 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. +* `MIRI_TEST_TARGET` (recognized by the test suite and the `./miri` script) indicates which target + architecture to test against. `miri` and `cargo miri` accept the `--target` flag for the same + purpose. +* `MIRI_NO_STD` (recognized by `cargo miri` and the test suite) makes sure that the target's + sysroot is built without libstd. This allows testing and running no_std programs. +* `MIRI_BLESS` (recognized by the test suite) 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 + `stderr` or `stdout` files match the actual output. Useful for the rustc test suite + which has subtle differences that we don't care about. + +The following environment variables are *internal* and must not be used by +anyone but Miri itself. They are used to communicate between different Miri +binaries, and as such worth documenting: + +* `MIRI_BE_RUSTC` can be set to `host` or `target`. It tells the Miri driver to + actually not interpret the code but compile it like rustc would. With `target`, Miri sets + some compiler flags to prepare the code for interpretation; with `host`, this is not done. + This environment variable is useful to be sure that the compiled `rlib`s are compatible + with Miri. +* `MIRI_CALLED_FROM_XARGO` is set during the Miri-induced `xargo` sysroot build, + which will re-invoke `cargo-miri` as the `rustc` to use for this build. +* `MIRI_CALLED_FROM_RUSTDOC` when set to any value tells `cargo-miri` that it is + running as a child process of `rustdoc`, which invokes it twice for each doc-test + and requires special treatment, most notably a check-only build before interpretation. + This is set by `cargo-miri` itself when running as a `rustdoc`-wrapper. +* `MIRI_CWD` when set to any value tells the Miri driver to change to the given + directory after loading all the source files, but before commencing + interpretation. This is useful if the interpreted program wants a different + working directory at run-time than at build-time. +* `MIRI_LOCAL_CRATES` is set by `cargo-miri` to tell the Miri driver which + crates should be given special treatment in diagnostics, in addition to the + crate currently being compiled. +* `MIRI_VERBOSE` when set to any value tells the various `cargo-miri` phases to + perform verbose logging. +* `MIRI_HOST_SYSROOT` is set by bootstrap to tell `cargo-miri` which sysroot to use for *host* + operations. + +[testing-miri]: CONTRIBUTING.md#testing-the-miri-driver + +## Miri `extern` functions + +Miri provides some `extern` functions that programs can import to access +Miri-specific functionality: + +```rust +#[cfg(miri)] +extern "Rust" { + /// Miri-provided extern function to mark the block `ptr` points to as a "root" + /// for some static memory. This memory and everything reachable by it is not + /// considered leaking even if it still exists when the program terminates. + /// + /// `ptr` has to point to the beginning of an allocated block. + fn miri_static_root(ptr: *const u8); + + // Miri-provided extern function to get the amount of frames in the current backtrace. + // The `flags` argument must be `0`. + fn miri_backtrace_size(flags: u64) -> usize; + + /// Miri-provided extern function to obtain a backtrace of the current call stack. + /// This writes a slice of pointers into `buf` - each pointer is an opaque value + /// that is only useful when passed to `miri_resolve_frame`. + /// `buf` must have `miri_backtrace_size(0) * pointer_size` bytes of space. + /// The `flags` argument must be `1`. + fn miri_get_backtrace(flags: u64, buf: *mut *mut ()); + + /// Miri-provided extern function to resolve a frame pointer obtained + /// from `miri_get_backtrace`. The `flags` argument must be `1`, + /// and `MiriFrame` should be declared as follows: + /// + /// ```rust + /// #[repr(C)] + /// struct MiriFrame { + /// // The size of the name of the function being executed, encoded in UTF-8 + /// name_len: usize, + /// // The size of filename of the function being executed, encoded in UTF-8 + /// filename_len: usize, + /// // The line number currently being executed in `filename`, starting from '1'. + /// lineno: u32, + /// // The column number currently being executed in `filename`, starting from '1'. + /// colno: u32, + /// // The function pointer to the function currently being executed. + /// // This can be compared against function pointers obtained by + /// // casting a function (e.g. `my_fn as *mut ()`) + /// fn_ptr: *mut () + /// } + /// ``` + /// + /// The fields must be declared in exactly the same order as they appear in `MiriFrame` above. + /// This function can be called on any thread (not just the one which obtained `frame`). + fn miri_resolve_frame(frame: *mut (), flags: u64) -> MiriFrame; + + /// Miri-provided extern function to get the name and filename of the frame provided by `miri_resolve_frame`. + /// `name_buf` and `filename_buf` should be allocated with the `name_len` and `filename_len` fields of `MiriFrame`. + /// The flags argument must be `0`. + fn miri_resolve_frame_names(ptr: *mut (), flags: u64, name_buf: *mut u8, filename_buf: *mut u8); + + /// Miri-provided extern function to begin unwinding with the given payload. + /// + /// This is internal and unstable and should not be used; we give it here + /// just to be complete. + fn miri_start_panic(payload: *mut u8) -> !; +} +``` + +## Contributing and getting help + +If you want to contribute to Miri, great! Please check out our +[contribution guide](CONTRIBUTING.md). + +For help with running Miri, you can open an issue here on +GitHub or use the [Miri stream on the Rust Zulip][zulip]. + +[zulip]: https://rust-lang.zulipchat.com/#narrow/stream/269128-miri + +## History + +This project began as part of an undergraduate research course in 2015 by +@solson at the [University of Saskatchewan][usask]. There are [slides] and a +[report] available from that project. In 2016, @oli-obk joined to prepare Miri +for eventually being used as const evaluator in the Rust compiler itself +(basically, for `const` and `static` stuff), replacing the old evaluator that +worked directly on the AST. In 2017, @RalfJung did an internship with Mozilla +and began developing Miri towards a tool for detecting undefined behavior, and +also using Miri as a way to explore the consequences of various possible +definitions for undefined behavior in Rust. @oli-obk's move of the Miri engine +into the compiler finally came to completion in early 2018. Meanwhile, later +that year, @RalfJung did a second internship, developing Miri further with +support for checking basic type invariants and verifying that references are +used according to their aliasing restrictions. + +[usask]: https://www.usask.ca/ +[slides]: https://solson.me/miri-slides.pdf +[report]: https://solson.me/miri-report.pdf + +## Bugs found by Miri + +Miri has already found a number of bugs in the Rust standard library and beyond, which we collect here. + +Definite bugs found: + +* [`Debug for vec_deque::Iter` accessing uninitialized memory](https://github.com/rust-lang/rust/issues/53566) +* [`Vec::into_iter` doing an unaligned ZST read](https://github.com/rust-lang/rust/pull/53804) +* [`From<&[T]> for Rc` creating a not sufficiently aligned reference](https://github.com/rust-lang/rust/issues/54908) +* [`BTreeMap` creating a shared reference pointing to a too small allocation](https://github.com/rust-lang/rust/issues/54957) +* [`Vec::append` creating a dangling reference](https://github.com/rust-lang/rust/pull/61082) +* [Futures turning a shared reference into a mutable one](https://github.com/rust-lang/rust/pull/56319) +* [`str` turning a shared reference into a mutable one](https://github.com/rust-lang/rust/pull/58200) +* [`rand` performing unaligned reads](https://github.com/rust-random/rand/issues/779) +* [The Unix allocator calling `posix_memalign` in an invalid way](https://github.com/rust-lang/rust/issues/62251) +* [`getrandom` calling the `getrandom` syscall in an invalid way](https://github.com/rust-random/getrandom/pull/73) +* [`Vec`](https://github.com/rust-lang/rust/issues/69770) and [`BTreeMap`](https://github.com/rust-lang/rust/issues/69769) leaking memory under some (panicky) conditions +* [`beef` leaking memory](https://github.com/maciejhirsz/beef/issues/12) +* [`EbrCell` using uninitialized memory incorrectly](https://github.com/Firstyear/concread/commit/b15be53b6ec076acb295a5c0483cdb4bf9be838f#diff-6282b2fc8e98bd089a1f0c86f648157cR229) +* [TiKV performing an unaligned pointer access](https://github.com/tikv/tikv/issues/7613) +* [`servo_arc` creating a dangling shared reference](https://github.com/servo/servo/issues/26357) +* [TiKV constructing out-of-bounds pointers (and overlapping mutable references)](https://github.com/tikv/tikv/pull/7751) +* [`encoding_rs` doing out-of-bounds pointer arithmetic](https://github.com/hsivonen/encoding_rs/pull/53) +* [TiKV using `Vec::from_raw_parts` incorrectly](https://github.com/tikv/agatedb/pull/24) +* Incorrect doctests for [`AtomicPtr`](https://github.com/rust-lang/rust/pull/84052) and [`Box::from_raw_in`](https://github.com/rust-lang/rust/pull/84053) +* [Insufficient alignment in `ThinVec`](https://github.com/Gankra/thin-vec/pull/27) +* [`crossbeam-epoch` calling `assume_init` on a partly-initialized `MaybeUninit`](https://github.com/crossbeam-rs/crossbeam/pull/779) +* [`integer-encoding` dereferencing a misaligned pointer](https://github.com/dermesser/integer-encoding-rs/pull/23) +* [`rkyv` constructing a `Box<[u8]>` from an overaligned allocation](https://github.com/rkyv/rkyv/commit/a9417193a34757e12e24263178be8b2eebb72456) +* [Data race in `thread::scope`](https://github.com/rust-lang/rust/issues/98498) +* [`regex` incorrectly handling unaligned `Vec` buffers](https://www.reddit.com/r/rust/comments/vq3mmu/comment/ienc7t0?context=3) +* [Incorrect use of `compare_exchange_weak` in `once_cell`](https://github.com/matklad/once_cell/issues/186) + +Violations of [Stacked Borrows] found that are likely bugs (but Stacked Borrows is currently just an experiment): + +* [`VecDeque::drain` creating overlapping mutable references](https://github.com/rust-lang/rust/pull/56161) +* Various `BTreeMap` problems + * [`BTreeMap` iterators creating mutable references that overlap with shared references](https://github.com/rust-lang/rust/pull/58431) + * [`BTreeMap::iter_mut` creating overlapping mutable references](https://github.com/rust-lang/rust/issues/73915) + * [`BTreeMap` node insertion using raw pointers outside their valid memory area](https://github.com/rust-lang/rust/issues/78477) +* [`LinkedList` cursor insertion creating overlapping mutable references](https://github.com/rust-lang/rust/pull/60072) +* [`Vec::push` invalidating existing references into the vector](https://github.com/rust-lang/rust/issues/60847) +* [`align_to_mut` violating uniqueness of mutable references](https://github.com/rust-lang/rust/issues/68549) +* [`sized-chunks` creating aliasing mutable references](https://github.com/bodil/sized-chunks/issues/8) +* [`String::push_str` invalidating existing references into the string](https://github.com/rust-lang/rust/issues/70301) +* [`ryu` using raw pointers outside their valid memory area](https://github.com/dtolnay/ryu/issues/24) +* [ink! creating overlapping mutable references](https://github.com/rust-lang/miri/issues/1364) +* [TiKV creating overlapping mutable reference and raw pointer](https://github.com/tikv/tikv/pull/7709) +* [Windows `Env` iterator using a raw pointer outside its valid memory area](https://github.com/rust-lang/rust/pull/70479) +* [`VecDeque::iter_mut` creating overlapping mutable references](https://github.com/rust-lang/rust/issues/74029) +* [Various standard library aliasing issues involving raw pointers](https://github.com/rust-lang/rust/pull/78602) +* [`<[T]>::copy_within` using a loan after invalidating it](https://github.com/rust-lang/rust/pull/85610) + +## Scientific papers employing Miri + +* [Stacked Borrows: An Aliasing Model for Rust](https://plv.mpi-sws.org/rustbelt/stacked-borrows/) +* [Using Lightweight Formal Methods to Validate a Key-Value Storage Node in Amazon S3](https://www.amazon.science/publications/using-lightweight-formal-methods-to-validate-a-key-value-storage-node-in-amazon-s3) +* [SyRust: Automatic Testing of Rust Libraries with Semantic-Aware Program Synthesis](https://dl.acm.org/doi/10.1145/3453483.3454084) + +## License + +Licensed under either of + + * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or + http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you shall be dual licensed as above, without any +additional terms or conditions. diff --git a/src/tools/miri/bench-cargo-miri/backtraces/Cargo.lock b/src/tools/miri/bench-cargo-miri/backtraces/Cargo.lock new file mode 100644 index 0000000000000..375b129a7e59b --- /dev/null +++ b/src/tools/miri/bench-cargo-miri/backtraces/Cargo.lock @@ -0,0 +1,94 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "backtrace" +version = "0.3.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11a17d453482a265fd5f8479f2a3f405566e6ca627837aaddb85af8b1ab8ef61" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "backtraces" +version = "0.1.0" +dependencies = [ + "backtrace", +] + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "gimli" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" + +[[package]] +name = "libc" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "miniz_oxide" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" +dependencies = [ + "adler", +] + +[[package]] +name = "object" +version = "0.28.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424" +dependencies = [ + "memchr", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" diff --git a/src/tools/miri/bench-cargo-miri/backtraces/Cargo.toml b/src/tools/miri/bench-cargo-miri/backtraces/Cargo.toml new file mode 100644 index 0000000000000..1ba96b19395d2 --- /dev/null +++ b/src/tools/miri/bench-cargo-miri/backtraces/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "backtraces" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +backtrace = "0.3.65" diff --git a/src/tools/miri/bench-cargo-miri/backtraces/src/main.rs b/src/tools/miri/bench-cargo-miri/backtraces/src/main.rs new file mode 100644 index 0000000000000..eba51c60dbc5b --- /dev/null +++ b/src/tools/miri/bench-cargo-miri/backtraces/src/main.rs @@ -0,0 +1,29 @@ +//! Extracted from the backtrace crate's test test_frame_conversion + +use backtrace::{Backtrace, BacktraceFrame}; +use std::fmt::Write; + +fn main() { + let mut frames = vec![]; + backtrace::trace(|frame| { + let converted = BacktraceFrame::from(frame.clone()); + frames.push(converted); + true + }); + + let mut manual = Backtrace::from(frames); + manual.resolve(); + let frames = manual.frames(); + + let mut output = String::new(); + for frame in frames { + // Originally these were println! but we'd prefer our benchmarks to not emit a lot of + // output to stdout/stderr. Unfortunately writeln! to a String is faster, but we still + // manage to exercise interesting code paths in Miri. + writeln!(output, "{:?}", frame.ip()).unwrap(); + writeln!(output, "{:?}", frame.symbol_address()).unwrap(); + writeln!(output, "{:?}", frame.module_base_address()).unwrap(); + writeln!(output, "{:?}", frame.symbols()).unwrap(); + } + drop(output); +} diff --git a/src/tools/miri/bench-cargo-miri/mse/Cargo.lock b/src/tools/miri/bench-cargo-miri/mse/Cargo.lock new file mode 100644 index 0000000000000..d2b1aa341bbb6 --- /dev/null +++ b/src/tools/miri/bench-cargo-miri/mse/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "mse" +version = "0.1.0" diff --git a/src/tools/miri/bench-cargo-miri/mse/Cargo.toml b/src/tools/miri/bench-cargo-miri/mse/Cargo.toml new file mode 100644 index 0000000000000..7b4c2dc758faa --- /dev/null +++ b/src/tools/miri/bench-cargo-miri/mse/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "mse" +version = "0.1.0" +authors = ["Ralf Jung "] +edition = "2018" + +[dependencies] diff --git a/src/tools/miri/bench-cargo-miri/mse/src/main.rs b/src/tools/miri/bench-cargo-miri/mse/src/main.rs new file mode 100644 index 0000000000000..c09dc2484d380 --- /dev/null +++ b/src/tools/miri/bench-cargo-miri/mse/src/main.rs @@ -0,0 +1,32 @@ +#[rustfmt::skip] // no need to wrap these arrays onto hundreds of lines +static EXPECTED: &[u8] = &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 1, 0, 255, 255, 1, 0, 255, 255, 1, 0, 255, 255, 1, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 254, 255, 0, 0, 254, 255, 0, 0, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 255, 255, 1, 0, 255, 255, 1, 0, 0, 0, 1, 0, 255, 255, 2, 0, 255, 255, 2, 0, 255, 255, 2, 0, 255, 255, 2, 0, 255, 255, 2, 0, 255, 255, 1, 0, 255, 255, 1, 0, 255, 255, 0, 0, 254, 255, 0, 0, 254, 255, 0, 0, 254, 255, 255, 255, 254, 255, 254, 255, 254, 255, 253, 255, 253, 255, 253, 255, 253, 255, 252, 255, 254, 255, 251, 255, 254, 255, 251, 255, 254, 255, 252, 255, 255, 255, 252, 255, 0, 0, 252, 255, 0, 0, 252, 255, 1, 0, 252, 255, 1, 0, 252, 255, 2, 0, 252, 255, 2, 0, 252, 255, 2, 0, 252, 255, 2, 0, 252, 255, 2, 0, 253, 255, 1, 0, 252, 255, 0, 0, 252, 255, 255, 255, 251, 255, 0, 0, 251, 255, 0, 0, 251, 255, 0, 0, 252, 255, 2, 0, 252, 255, 3, 0, 252, 255, 4, 0, 253, 255, 5, 0, 254, 255, 5, 0, 253, 255, 6, 0, 253, 255, 6, 0, 253, 255, 5, 0, 253, 255, 5, 0, 254, 255, 4, 0, 254, 255, 3, 0, 251, 255, 0, 0, 250, 255, 255, 255, 253, 255, 254, 255, 252, 255, 252, 255, 247, 255, 251, 255, 247, 255, 252, 255, 252, 255, 254, 255, 252, 255, 254, 255, 252, 255, 255, 255, 254, 255, 1, 0, 1, 0, 1, 0, 4, 0, 2, 0, 8, 0, 2, 0, 12, 0, 1, 0, 13, 0, 0, 0, 12, 0, 0, 0, 11, 0, 255, 255, 8, 0, 254, 255, 7, 0, 0, 0, 7, 0, 253, 255, 11, 0, 248, 255, 15, 0, 247, 255, 17, 0, 250, 255, 17, 0, 251, 255, 13, 0, 253, 255, 7, 0, 255, 255, 3, 0, 255, 255, 254, 255, 255, 255, 252, 255, 255, 255, 252, 255, 254, 255, 250, 255, 255, 255, 242, 255, 254, 255, 239, 255, 252, 255, 248, 255, 255, 255, 249, 255, 5, 0, 239, 255, 7, 0, 238, 255, 10, 0, 249, 255, 18, 0, 254, 255, 25, 0, 253, 255, 27, 0, 0, 0, 31, 0, 4, 0, 34, 0, 4, 0, 34, 0, 8, 0, 36, 0, 8, 0, 37, 0, 2, 0, 36, 0, 4, 0, 34, 0, 8, 0, 28, 0, 3, 0, 15, 0, 255, 255, 11, 0, 0, 0, 12, 0, 251, 255, 8, 0, 252, 255, 10, 0, 0, 0, 23, 0, 252, 255, 31, 0, 248, 255, 30, 0, 254, 255, 30, 0, 255, 255, 26, 0, 250, 255, 22, 0, 250, 255, 20, 0, 244, 255, 15, 0, 237, 255, 10, 0, 246, 255, 13, 0, 242, 255, 6, 0, 213, 255, 243, 255, 213, 255, 240, 255, 247, 255, 244, 255, 246, 255, 227, 255, 214, 255, 216, 255, 219, 255, 228, 255, 251, 255, 235, 255, 1, 0, 232, 255, 248, 255, 236, 255, 4, 0, 238, 255, 26, 0, 232, 255, 44, 0, 230, 255, 66, 0, 226, 255, 86, 0, 219, 255, 88, 0, 215, 255, 72, 0, 210, 255, 50, 0, 225, 255, 28, 0, 23, 0, 14, 0, 64, 0, 16, 0, 51, 0, 26, 0, 32, 0, 34, 0, 39, 0, 42, 0, 48, 0, 35, 0, 58, 0, 255, 255, 72, 0, 220, 255, 69, 0, 197, 255, 58, 0, 158, 255, 54, 0, 132, 255, 36, 0, 153, 255, 12, 0, 146, 255, 5, 0, 83, 255, 237, 255, 110, 255, 197, 255, 252, 255, 214, 255, 51, 0, 1, 0, 233, 255, 250, 255, 226, 255, 250, 255, 45, 0, 46, 0, 47, 0, 70, 0, 6, 0, 55, 0, 19, 0, 60, 0, 38, 0, 62, 0, 42, 0, 47, 0, 61, 0, 46, 0, 40, 0, 42, 0, 237, 255, 22, 0, 222, 255, 6, 0, 221, 255, 206, 255, 195, 255, 115, 255, 219, 255, 85, 255, 17, 0, 93, 255, 26, 0, 76, 255, 46, 0, 102, 255, 80, 0, 193, 255, 48, 0, 252, 255, 18, 0, 20, 0, 50, 0, 47, 0, 58, 0, 53, 0, 44, 0, 61, 0, 57, 0, 85, 0, 37, 0, 80, 0, 0, 0, 86, 0, 248, 255, 106, 0, 161, 255, 49, 0, 43, 255, 248, 255, 125, 255, 47, 0, 49, 0, 63, 0, 40, 0, 217, 255, 187, 255, 182, 255, 219, 255, 236, 255, 63, 0, 244, 255, 58, 0, 242, 255, 244, 255, 25, 0, 225, 255, 41, 0, 11, 0, 45, 0, 76, 0, 47, 0, 167, 0, 5, 0, 5, 1, 219, 255, 21, 1, 173, 255, 183, 0, 84, 255, 35, 0, 134, 255, 177, 255, 138, 0, 186, 255, 10, 1, 69, 0, 124, 0, 228, 0, 0, 0, 135, 1, 227, 255, 82, 2, 172, 255, 190, 2, 178, 255, 115, 2, 248, 255, 39, 2, 243, 255, 253, 1, 13, 0, 116, 1, 120, 0, 96, 1, 125, 0, 110, 2, 127, 0, 179, 2, 223, 0, 106, 1, 126, 0, 130, 1, 223, 255, 147, 3, 198, 0, 190, 3, 201, 1, 200, 1, 42, 1, 244, 1, 233, 0, 3, 4, 213, 1, 72, 4, 170, 1, 150, 3, 160, 0, 43, 4, 141, 0, 196, 4, 189, 0, 221, 4, 164, 0, 95, 5, 41, 1, 98, 5, 247, 1, 19, 5, 190, 2, 14, 6, 161, 3, 7, 7, 87, 3, 216, 6, 35, 2, 38, 7, 90, 2, 136, 7, 64, 3, 200, 6, 28, 3, 199, 6, 165, 3, 169, 7, 105, 5, 143, 7, 26, 6, 57, 8, 205, 5, 156, 10, 169, 5, 132, 11, 25, 5, 208, 10, 181, 4, 156, 10, 66, 5, 227, 9, 170, 5, 166, 9, 117, 6, 45, 12, 63, 8, 42, 13, 128, 8, 136, 10, 155, 7, 109, 11, 1, 9, 6, 15, 98, 10, 121, 9, 136, 8, 147, 252, 189, 7, 43, 247, 63, 10, 147, 249, 92, 11, 172, 248, 172, 10, 112, 245, 137, 11, 76, 246, 44, 12, 184, 247, 138, 11, 118, 246, 144, 12, 94, 246, 171, 13, 112, 247, 162, 12, 168, 246, 33, 13, 63, 246, 29, 15, 226, 247, 188, 14, 190, 248, 75, 15, 238, 247, 86, 17, 19, 247, 118, 11, 10, 247, 232, 254, 238, 247, 30, 249, 56, 248, 124, 250, 6, 247, 1, 250, 161, 246, 3, 249, 81, 247, 117, 250, 60, 247, 202, 250, 212, 247, 60, 250, 15, 249, 140, 250, 34, 248, 221, 249, 105, 247, 218, 249, 205, 248, 113, 251, 138, 248, 90, 250, 41, 248, 230, 248]; +#[rustfmt::skip] +static PCM: &[i16] = &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, -2, 0, -2, 0, -2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 1, 0, 1, 0, 0, -2, 0, -2, 0, -2, 0, -2, -2, -2, -3, -3, -3, -3, -4, -2, -5, -2, -5, -2, -4, 0, -4, 0, -4, 0, -4, 1, -4, 1, -4, 2, -4, 2, -4, 2, -4, 2, -4, 2, -3, 1, -4, 0, -4, 0, -5, 0, -5, 0, -5, 0, -4, 2, -4, 3, -4, 4, -3, 5, -2, 5, -3, 6, -3, 6, -3, 5, -3, 5, -2, 4, -2, 3, -5, 0, -6, 0, -3, -2, -4, -4, -9, -5, -9, -4, -4, -2, -4, -2, -4, 0, -2, 1, 1, 1, 4, 2, 8, 2, 12, 1, 13, 0, 12, 0, 11, 0, 8, -2, 7, 0, 7, -3, 11, -8, 15, -9, 17, -6, 17, -5, 13, -3, 7, 0, 3, 0, -2, 0, -4, 0, -4, -2, -6, 0, -14, -2, -17, -4, -8, 0, -7, 5, -17, 7, -18, 10, -7, 18, -2, 25, -3, 27, 0, 31, 4, 34, 4, 34, 8, 36, 8, 37, 2, 36, 4, 34, 8, 28, 3, 15, 0, 11, 0, 12, -5, 8, -4, 10, 0, 23, -4, 31, -8, 30, -2, 30, 0, 26, -6, 22, -6, 20, -12, 15, -19, 10, -10, 13, -14, 6, -43, -13, -43, -16, -9, -12, -10, -29, -42, -40, -37, -28, -5, -21, 1, -24, -8, -20, 4, -18, 26, -24, 44, -26, 66, -30, 86, -37, 88, -41, 72, -46, 50, -31, 28, 23, 14, 64, 16, 51, 26, 32, 34, 39, 42, 48, 35, 58, 0, 72, -36, 69, -59, 58, -98, 54, -124, 36, -103, 12, -110, 5, -173, -19, -146, -59, -4, -42, 51, 1, -23, -6, -30, -6, 45, 46, 47, 70, 6, 55, 19, 60, 38, 62, 42, 47, 61, 46, 40, 42, -19, 22, -34, 6, -35, -50, -61, -141, -37, -171, 17, -163, 26, -180, 46, -154, 80, -63, 48, -4, 18, 20, 50, 47, 58, 53, 44, 61, 57, 85, 37, 80, 0, 86, -8, 106, -95, 49, -213, -8, -131, 47, 49, 63, 40, -39, -69, -74, -37, -20, 63, -12, 58, -14, -12, 25, -31, 41, 11, 45, 76, 47, 167, 5, 261, -37, 277, -83, 183, -172, 35, -122, -79, 138, -70, 266, 69, 124, 228, 0, 391, -29, 594, -84, 702, -78, 627, -8, 551, -13, 509, 13, 372, 120, 352, 125, 622, 127, 691, 223, 362, 126, 386, -33, 915, 198, 958, 457, 456, 298, 500, 233, 1027, 469, 1096, 426, 918, 160, 1067, 141, 1220, 189, 1245, 164, 1375, 297, 1378, 503, 1299, 702, 1550, 929, 1799, 855, 1752, 547, 1830, 602, 1928, 832, 1736, 796, 1735, 933, 1961, 1385, 1935, 1562, 2105, 1485, 2716, 1449, 2948, 1305, 2768, 1205, 2716, 1346, 2531, 1450, 2470, 1653, 3117, 2111, 3370, 2176, 2696, 1947, 2925, 2305, 3846, 2658, 2425, 2184, -877, 1981, -2261, 2623, -1645, 2908, -1876, 2732, -2704, 2953, -2484, 3116, -2120, 2954, -2442, 3216, -2466, 3499, -2192, 3234, -2392, 3361, -2497, 3869, -2078, 3772, -1858, 3915, -2066, 4438, -2285, 2934, -2294, -280, -2066, -1762, -1992, -1412, -2298, -1535, -2399, -1789, -2223, -1419, -2244, -1334, -2092, -1476, -1777, -1396, -2014, -1571, -2199, -1574, -1843, -1167, -1910, -1446, -2007, -1818]; + +fn main() { + #[cfg(increase_thread_usage)] + let thread = std::thread::spawn(|| 4); + + for _ in 0..2 { + mse(PCM.len(), PCM, EXPECTED); + } +} + +fn read_i16(buffer: &[u8], index: usize) -> i16 { + const SIZE: usize = std::mem::size_of::(); + let mut bytes: [u8; SIZE] = [0u8; SIZE]; + bytes.copy_from_slice(&buffer[(index * SIZE)..(index * SIZE + SIZE)]); + unsafe { std::mem::transmute(bytes) } +} + +fn mse(samples: usize, frame_buf: &[i16], buf_ref: &[u8]) -> f64 { + let mut mse = 0.0; + let max_samples = std::cmp::min(buf_ref.len() / 2, samples as usize); + for i in 0..max_samples { + let ref_res = read_i16(buf_ref, i); + let info_res = frame_buf[i as usize]; + let diff = (ref_res - info_res).abs(); + mse += f64::from(diff.pow(2)); + } + mse / max_samples as f64 +} diff --git a/src/tools/miri/bench-cargo-miri/serde1/Cargo.lock b/src/tools/miri/bench-cargo-miri/serde1/Cargo.lock new file mode 100644 index 0000000000000..4875057613543 --- /dev/null +++ b/src/tools/miri/bench-cargo-miri/serde1/Cargo.lock @@ -0,0 +1,89 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "cargo-miri-test" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "itoa" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" + +[[package]] +name = "proc-macro2" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" + +[[package]] +name = "serde" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "syn" +version = "1.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" diff --git a/src/tools/miri/bench-cargo-miri/serde1/Cargo.toml b/src/tools/miri/bench-cargo-miri/serde1/Cargo.toml new file mode 100644 index 0000000000000..7cb863a7abf33 --- /dev/null +++ b/src/tools/miri/bench-cargo-miri/serde1/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "cargo-miri-test" +version = "0.1.0" +authors = ["Oliver Schneider "] +edition = "2018" + +[dependencies] +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" diff --git a/src/tools/miri/bench-cargo-miri/serde1/src/main.rs b/src/tools/miri/bench-cargo-miri/serde1/src/main.rs new file mode 100644 index 0000000000000..2712156d61b4a --- /dev/null +++ b/src/tools/miri/bench-cargo-miri/serde1/src/main.rs @@ -0,0 +1,13 @@ +static JSON: &str = r#"{"buffer":[-29,-42,-40,-37,-28,-5,-21,1,-24,-8,-20,4,-18,26,-24,44,-26,66,-30,86,-37,88,-41,72,-46,50,-31,28,23,14,64,16,51,26,32,34,39,42,48,35,58,0,72,-36,69,-59,58,-98,54,-124,36,-103,12,-110,5,-173,-19,-146,-59,-4,-42,51,1,-23,-6,-30,-6,45,46,47,70,6,55,19,60,38,62,42,47,61,46,40,42,-19,22,-34,6,-35,-50,-61,-141,-37,-171,17,-163,26,-180,46,-154,80,-63,48,-4,18,20,50,47,58,53,44,61,57,85,37,80,0,86,-8,106,-95,49,-213,-8,-131,47,49,63,40,-39,-69,-74,-37,-20,63,-12,58,-14,-12,25,-31,41,11,45,76,47,167,5,261,-37,277,-83,183,-172,35,-122,-79,138,-70,266,69,124,228,0,391,-29,594,-84,702,-78,627,-8,551,-13,509,13,372,120,352,125,622,127,691,223,362,126,386,-33,915,198,958,457,456,298,500,233,1027,469,1096,426,918,160,1067,141,1220,189,1245,164,1375,297,1378,503,1299,702,1550,929,1799,855,1752,547,1830,602,1928,832,1736,796,1735,933,1961,1385,1935,1562,2105,1485,2716,1449,2948,1305,2768,1205,2716,1346,2531,1450,2470,1653,3117,2111,3370,2176,2696,1947,2925,2305,3846,2658,2425,2184,-877,1981,-2261,2623,-1645,2908,-1876,2732,-2704,2953,-2484,3116,-2120,2954,-2442,3216,-2466,3499,-2192,3234,-2392,3361,-2497,3869,-2078,3772,-1858,3915,-2066,4438,-2285,2934,-2294,-280,-2066,-1762,-1992,-1412,-2298,-1535,-2399,-1789,-2223,-1419,-2244,-1334,-2092,-1476,-1777,-1396,-2014,-1571,-2199,-1574,-1843,-1167,-1910,-1446,-2007,-1818,-1506,-1331,-2526,-2048,-5535,-4573,-7148,-5828,-6422,-5327,-5840,-5488,-5992,-6144,-6014,-6164,-6109,-6234,-6271,-6388,-6288,-6156,-6517,-6249,-6794,-6602,-6822,-6418,-6788,-6245,-6490,-6560,-6394,-6794,-7920,-6937,-10397,-7140,-11428,-6972,-11019,-6610,-11141,-6665,-11913,-7046,-11979,-7235,-11599,-7015,-11854,-6912,-12161,-7441,-12136,-7761,-12861,-7292,-13390,-7254,-12345,-7809,-12490,-7463,-13983,-6969,-10489,-8465,-2382,-11054,1272,-12247,-270,-12060,-323,-12113,502,-12486,-697,-12251,-1086,-12141,-181,-13116,-670,-13509,-1173,-12592,-443,-12811,-449,-13698,-934,-12850,-747,-13083,-873,-15036,-1161,-11478,-1047,-2669,-1407,1006,-1658,-1146,-1195,-1297,-1421,-73,-1946,-977,-1590,-1499,-1577,-1010,-1862,-1256,-1389,-962,-1692,-509,-2613,-1317,-2087,-1359,-1997,-1034,-2891,-2024,-119,-84,5651,5723,8074,8306,7156,6870,6985,7106,7312,8403,7114,8096,7173,7848,7082,7827,6761,7189,6985,7368,7076,7835,6992,7297,7453,7260,7016,7755,6025,7429,8533,7352,14150,7628,17142,7077,16399,6947,15939,7475,16564,7069,16463,6882,16400,7602,17031,7233,16543,6517,15395,7018,15985,7104,16689,6869,15655,7622,16155,7198,17884,6022,14056,8856,5665,14484,1815,16782,3034,15786,3107,15664,2312,16517,2965,16443,3036,16120,2287,16584,2479,16720,2693,16073,2535,16159,2958,16609,3067,16086,2716,16579,3035,17752,3092,13704,2499,5265,2620,1452,2808,3024,2444,3275,2839,2267,3340,2857,2968,3232,3066,2867,3152,3072,2248,2961,2413,2807,3238,3237,2368,2699,2262,2392,3537,3339,827,823,-5020,-5359,-7095,-7857,-5973,-6274,-6208,-6279,-6934,-7181,-6893,-6647,-7146,-6687,-7026,-7328,-6451,-6924,-6763,-6535,-7109,-6639,-6926,-6559,-7188,-6799,-6727,-6955,-5786,-6554,-8543,-6796,-14465,-7190,-17356,-6641,-16372,-6529,-15941,-6898,-16526,-6434,-16219,-6520,-16222,-7449,-17077,-7097,-16665,-6476,-15675,-7026,-16498,-6848,-17147,-6271,-15894,-7069,-16266,-7032,-17817,-5991,-13796,-8594,-5421,-14349,-1649,-17288,-2847,-16525,-2974,-15945,-2324,-16482,-3022,-16593,-3097,-16451,-2420,-16780,-2649,-16641,-2836,-15900,-2660,-16214,-3050,-16827,-3111,-15993,-2741,-16151,-2994,-17537,-2933,-13812,-2314,-5216,-2475,-1125,-2648,-2801,-2290,-3285,-2796,-2243,-3415,-2642,-3109,-3000,-3271,-2839,-3408,-3161,-2497,-2876,-2603,-2570,-3351,-3173,-2416,-2832,-2235,-2408,-3405,-3186,-613,-768,5271,5201,7376,7644,6241,6176,6366,6275,6964,7124,6831,6508,6998,6566,6836,7230,6277,6777,6589,6376,6934,6536,6819,6494,7160,6749,6736,6900,5822,6476,8593,6747,14520,7204,17448,6637,16490,6483,16033,6906,16600,6511,16304,6568,16279,7438,17079,7072,16624,6463,15577,7028,16343,6877,16990,6331,15760,7121,16140,7023,17719,5944,13748,8575,5401,14336,1645,17210,2880,16419,3036,15896,2382,16483,3074,16584,3143,16425,2443,16782,2650,16695,2825,15978,2632,16272,3015,16880,3084,16096,2709,16289,2965,17641,2932,13887,2323,5330,2474,1286,2656,2954,2309,3410,2803,2373,3414,2795,3106,3151,3263,2952,3403,3241,2483,2969,2568,2681,3316,3245,2383,2837,2199,2390,3396,3165,641,706,-5230,-5323,-7307,-7790,-6136,-6317,-6268,-6419,-6884,-7278,-6766,-6666,-6976,-6731,-6853,-7406,-6308,-6958,-6636,-6553,-6978,-6703,-6829,-6647,-7156,-6883,-6737,-7017,-5814,-6581,-8575,-6833,-14490,-7270,-17411,-6699,-16466,-6539,-16016,-6931,-16571,-6504,-16257,-6551,-16202,-7408,-16983,-7021,-16545,-6410,-15512,-6976,-16305,-6803,-17017,-6243,-15820,-7037,-16197,-6923,-17802,-5820,-13840,-8455,-5475,-14227,-1724,-17099,-2923,-16314,-3008,-15801,-2362,-16392,-3088,-16506,-3163,-16356,-2503,-16700,-2717,-16605,-2855,-15904,-2710,-16226,-3108,-16870,-3089,-16101,-2747,-16257,-3087,-17584,-2975,-13868,-2324,-5343,-2548,-1275,-2673,-2917,-2213,-3363,-2694,-2311,-3251,-2744,-2867,-3129,-3034,-2939,-3190,-3234,-2346,-2964,-2639,-2658,-3558,-3241,-2670,-2892,-2453,-2437,-3564,-3175,-771,-779,5105,5171,7308,7655,6265,6204,6397,6288,7024,7172,6903,6586,7002,6627,6777,7308,6190,6889,6537,6465,7011,6613,6985,6631,7393,6934,7073,7072,6112,6615,8751,6859,14672,7282,17448,6652,16146,6448,15565,6899,16151,6547,15860,6591,16048,7446,17065,7064,16661,6368,15774,6857,16524,6677,16825,6071,15577,6900,16119,7040,17490,6118,13495,8696,5432,14446,1678,17366,3036,16488,3624,15834,3012,16382,3575,16465,3685,16301,2815,16708,2982,16679,3356,15952,2934,16049,3290,16352,3964,15605,3612,16222,3647,17764,4272,13865,3977,5384,3592,1580,3794,3243,3627,3670,3622,2758,4007,3130,3835,3294,3964,3065,4468,3408,3933,3234,3789,3118,4634,3643,4211,3174,4155,3176,5512,4400,2792,1730,-3702,-4499,-5940,-6691,-4265,-5094,-4381,-5215,-4918,-5746,-4217,-4871,-4402,-4981,-4479,-5525,-3732,-4968,-4118,-4924,-4300,-5349,-3422,-5021,-3876,-4886,-4087,-4860,-2790,-4254,-5025,-4196,-10898,-4415,-13419,-4007,-12198,-4121,-11995,-4413,-12471,-3808,-11937,-3920,-11792,-4583,-12284,-3776,-12085,-3107,-11421,-3583,-11226,-3081,-11157,-2768,-10580,-3914,-10424,-3197,-11040,-1715,-9822,-5144,-6189,-11154,-4236,-13029,-5134,-11598,-5507,-10949,-4921,-11142,-4999,-11180,-4883,-11184,-4366,-11090,-4548,-10887,-4818,-10708,-4866,-10534,-5253,-10272,-5179,-9894,-4633,-10029,-4773,-10382,-4977,-8674,-4668,-5292,-4651,-3928,-4629,-4465,-4312,-3994,-4459,-3528,-4570,-4400,-4272,-4601,-4482,-4035,-4627,-4334,-4080,-4498,-4045,-3835,-4204,-3526,-3695,-3646,-4045,-4101,-4856,-4628,-3338,-3235,-673,-508,28,147,-453,-639,11,0,8,-2,7,0,7,-3,11,-8,15,-9,17,-6,17,-5,13,-3,7,0,3,0,-2,0,-4,0,-4,-2,-6,0,-14,-2,-17,-4,-8,0,-7,5,-17,7,-18,10,-7,18,-2,25,-3,27,0,31,4,34,4,34,8,36,8,37,2,36,4,34,8,28,3,15,0,11,0,12,-5,8,-4,10,0,23,-4,31,-8,30,-2,30,0,26,-6,22,-6,20,-12,15,-19,10,-10,13,-14,6,-43,-13,-43,-16,-9,-12,-10,-29,-42,-40,-37,-28,-5,-21,1,-24,-8,-20,4,-18,26,-24,44,-26,66,-30,86,-37,88,-41,72,-46,50,-31,28,23,14,64,16,51,26,32,34,39,42,48,35,58,0,72,-36,69,-59,58,-98,54,-124,36,-103,12,-110,5,-173,-19,-146,-59,-4,-42,51,1,-23,-6,-30,-6,45,46,47,70,6,55,19,60,38,62,42,47,61,46,40,42,-19,22,-34,6,-35,-50,-61,-141,-37,-171,17,-163,26,-180,46,-154,80,-63,48,-4,18,20,50,47,58,53,44,61,57,85,37,80,0,86,-8,106,-95,49,-213,-8,-131,47,49,63,40,-39,-69,-74,-37,-20,63,-12,58,-14,-12,25,-31,41,11,45,76,47,167,5,261,-37,277,-83,183,-172,35,-122,-79,138,-70,266,69,124,228,0,391,-29,594,-84,702,-78,627,-8,551,-13,509,13,372,120,352,125,622,127,691,223,362,126,386,-33,915,198,958,457,456,298,500,233,1027,469,1096,426,918,160,1067,141,1220,189,1245,164,1375,297,1378,503,1299,702,1550,929,1799,855,1752,547,1830,602,1928,832,1736,796,1735,933,1961,1385,1935,1562,2105,1485,2716,1449,2948,1305,2768,1205,2716,1346,2531,1450,2470,1653,3117,2111,3370,2176,2696,1947,2925,2305,3846,2658,2425,2184,-877,1981,-2261,2623,-1645,2908,-1876,2732,-2704,2953,-2484,3116,-2120,2954,-2442,3216,-2466,3499,-2192,3234,-2392,3361,-2497,3869,-2078,3772,-1858,3915,-2066,4438,-2285,2934,-2294,-280,-2066,-1762,-1992,-1412,-2298,-1535,-2399,-1789,-2223,-1419,-2244,-1334,-2092,-1476,-1777,-1396,-2014,-1571,-2199,-1574,-1843,-1167,-1910,-1446,-2007,-1818,-1506,-1331,-2526,-2048,-5535,-4573,-7148,-5828,-6422,-5327,-5840,-5488,-5992,-6144,-6014,-6164,-6109,-6234,-6271,-6388,-6288,-6156,-6517,-6249,-6794,-6602,-6822,-6418,-6788,-6245,-6490,-6560,-6394,-6794,-7920,-6937,-10397,-7140,-11428,-6972,-11019,-6610,-11141,-6665,-11913,-7046,-11979,-7235,-11599,-7015,-11854,-6912,-12161,-7441,-12136,-7761,-12861,-7292,-13390,-7254,-12345,-7809,-12490,-7463,-13983,-6969,-10489,-8465,-2382,-11054,1272,-12247,-270,-12060,-323,-12113,502,-12486,-697,-12251,-1086,-12141,-181,-13116,-670,-13509,-1173,-12592,-443,-12811,-449,-13698,-934,-12850,-747,-13083,-873,-15036,-1161,-11478,-1047,-2669,-1407,1006,-1658,-1146,-1195,-1297,-1421,-73,-1946,-977,-1590,-1499,-1577,-1010,-1862,-1256,-1389,-962,-1692,-509,-2613,-1317,-2087,-1359,-1997,-1034,-2891,-2024,-119,-84,5651,5723,8074,8306,7156,6870,6985,7106,7312,8403,7114,8096,7173,7848,7082,7827,6761,7189,6985,7368]}"#; + +use serde::Deserialize; + +#[derive(Deserialize)] +struct DeriveStruct { + buffer: Vec, +} + +fn main() { + let info: DeriveStruct = serde_json::from_str(JSON).unwrap(); + println!("{}", info.buffer.len()); +} diff --git a/src/tools/miri/bench-cargo-miri/serde2/Cargo.lock b/src/tools/miri/bench-cargo-miri/serde2/Cargo.lock new file mode 100644 index 0000000000000..4875057613543 --- /dev/null +++ b/src/tools/miri/bench-cargo-miri/serde2/Cargo.lock @@ -0,0 +1,89 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "cargo-miri-test" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "itoa" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" + +[[package]] +name = "proc-macro2" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" + +[[package]] +name = "serde" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "syn" +version = "1.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" diff --git a/src/tools/miri/bench-cargo-miri/serde2/Cargo.toml b/src/tools/miri/bench-cargo-miri/serde2/Cargo.toml new file mode 100644 index 0000000000000..7cb863a7abf33 --- /dev/null +++ b/src/tools/miri/bench-cargo-miri/serde2/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "cargo-miri-test" +version = "0.1.0" +authors = ["Oliver Schneider "] +edition = "2018" + +[dependencies] +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" diff --git a/src/tools/miri/bench-cargo-miri/serde2/src/main.rs b/src/tools/miri/bench-cargo-miri/serde2/src/main.rs new file mode 100644 index 0000000000000..c81b32c13fefb --- /dev/null +++ b/src/tools/miri/bench-cargo-miri/serde2/src/main.rs @@ -0,0 +1,18 @@ +// Like serde1, but in two threads concurrently. And we don't print. +static JSON: &str = r#"{"buffer":[-29,-42,-40,-37,-28,-5,-21,1,-24,-8,-20,4,-18,26,-24,44,-26,66,-30,86,-37,88,-41,72,-46,50,-31,28,23,14,64,16,51,26,32,34,39,42,48,35,58,0,72,-36,69,-59,58,-98,54,-124,36,-103,12,-110,5,-173,-19,-146,-59,-4,-42,51,1,-23,-6,-30,-6,45,46,47,70,6,55,19,60,38,62,42,47,61,46,40,42,-19,22,-34,6,-35,-50,-61,-141,-37,-171,17,-163,26,-180,46,-154,80,-63,48,-4,18,20,50,47,58,53,44,61,57,85,37,80,0,86,-8,106,-95,49,-213,-8,-131,47,49,63,40,-39,-69,-74,-37,-20,63,-12,58,-14,-12,25,-31,41,11,45,76,47,167,5,261,-37,277,-83,183,-172,35,-122,-79,138,-70,266,69,124,228,0,391,-29,594,-84,702,-78,627,-8,551,-13,509,13,372,120,352,125,622,127,691,223,362,126,386,-33,915,198,958,457,456,298,500,233,1027,469,1096,426,918,160,1067,141,1220,189,1245,164,1375,297,1378,503,1299,702,1550,929,1799,855,1752,547,1830,602,1928,832,1736,796,1735,933,1961,1385,1935,1562,2105,1485,2716,1449,2948,1305,2768,1205,2716,1346,2531,1450,2470,1653,3117,2111,3370,2176,2696,1947,2925,2305,3846,2658,2425,2184,-877,1981,-2261,2623,-1645,2908,-1876,2732,-2704,2953,-2484,3116,-2120,2954,-2442,3216,-2466,3499,-2192,3234,-2392,3361,-2497,3869,-2078,3772,-1858,3915,-2066,4438,-2285,2934,-2294,-280,-2066,-1762,-1992,-1412,-2298,-1535,-2399,-1789,-2223,-1419,-2244,-1334,-2092,-1476,-1777,-1396,-2014,-1571,-2199,-1574,-1843,-1167,-1910,-1446,-2007,-1818,-1506,-1331,-2526,-2048,-5535,-4573,-7148,-5828,-6422,-5327,-5840,-5488,-5992,-6144,-6014,-6164,-6109,-6234,-6271,-6388,-6288,-6156,-6517,-6249,-6794,-6602,-6822,-6418,-6788,-6245,-6490,-6560,-6394,-6794,-7920,-6937,-10397,-7140,-11428,-6972,-11019,-6610,-11141,-6665,-11913,-7046,-11979,-7235,-11599,-7015,-11854,-6912,-12161,-7441,-12136,-7761,-12861,-7292,-13390,-7254,-12345,-7809,-12490,-7463,-13983,-6969,-10489,-8465,-2382,-11054,1272,-12247,-270,-12060,-323,-12113,502,-12486,-697,-12251,-1086,-12141,-181,-13116,-670,-13509,-1173,-12592,-443,-12811,-449,-13698,-934,-12850,-747,-13083,-873,-15036,-1161,-11478,-1047,-2669,-1407,1006,-1658,-1146,-1195,-1297,-1421,-73,-1946,-977,-1590,-1499,-1577,-1010,-1862,-1256,-1389,-962,-1692,-509,-2613,-1317,-2087,-1359,-1997,-1034,-2891,-2024,-119,-84,5651,5723,8074,8306,7156,6870,6985,7106,7312,8403,7114,8096,7173,7848,7082,7827,6761,7189,6985,7368,7076,7835,6992,7297,7453,7260,7016,7755,6025,7429,8533,7352,14150,7628,17142,7077,16399,6947,15939,7475,16564,7069,16463,6882,16400,7602,17031,7233,16543,6517,15395,7018,15985,7104,16689,6869,15655,7622,16155,7198,17884,6022,14056,8856,5665,14484,1815,16782,3034,15786,3107,15664,2312,16517,2965,16443,3036,16120,2287,16584,2479,16720,2693,16073,2535,16159,2958,16609,3067,16086,2716,16579,3035,17752,3092,13704,2499,5265,2620,1452,2808,3024,2444,3275,2839,2267,3340,2857,2968,3232,3066,2867,3152,3072,2248,2961,2413,2807,3238,3237,2368,2699,2262,2392,3537,3339,827,823,-5020,-5359,-7095,-7857,-5973,-6274,-6208,-6279,-6934,-7181,-6893,-6647,-7146,-6687,-7026,-7328,-6451,-6924,-6763,-6535,-7109,-6639,-6926,-6559,-7188,-6799,-6727,-6955,-5786,-6554,-8543,-6796,-14465,-7190,-17356,-6641,-16372,-6529,-15941,-6898,-16526,-6434,-16219,-6520,-16222,-7449,-17077,-7097,-16665,-6476,-15675,-7026,-16498,-6848,-17147,-6271,-15894,-7069,-16266,-7032,-17817,-5991,-13796,-8594,-5421,-14349,-1649,-17288,-2847,-16525,-2974,-15945,-2324,-16482,-3022,-16593,-3097,-16451,-2420,-16780,-2649,-16641,-2836,-15900,-2660,-16214,-3050,-16827,-3111,-15993,-2741,-16151,-2994,-17537,-2933,-13812,-2314,-5216,-2475,-1125,-2648,-2801,-2290,-3285,-2796,-2243,-3415,-2642,-3109,-3000,-3271,-2839,-3408,-3161,-2497,-2876,-2603,-2570,-3351,-3173,-2416,-2832,-2235,-2408,-3405,-3186,-613,-768,5271,5201,7376,7644,6241,6176,6366,6275,6964,7124,6831,6508,6998,6566,6836,7230,6277,6777,6589,6376,6934,6536,6819,6494,7160,6749,6736,6900,5822,6476,8593,6747,14520,7204,17448,6637,16490,6483,16033,6906,16600,6511,16304,6568,16279,7438,17079,7072,16624,6463,15577,7028,16343,6877,16990,6331,15760,7121,16140,7023,17719,5944,13748,8575,5401,14336,1645,17210,2880,16419,3036,15896,2382,16483,3074,16584,3143,16425,2443,16782,2650,16695,2825,15978,2632,16272,3015,16880,3084,16096,2709,16289,2965,17641,2932,13887,2323,5330,2474,1286,2656,2954,2309,3410,2803,2373,3414,2795,3106,3151,3263,2952,3403,3241,2483,2969,2568,2681,3316,3245,2383,2837,2199,2390,3396,3165,641,706,-5230,-5323,-7307,-7790,-6136,-6317,-6268,-6419,-6884,-7278,-6766,-6666,-6976,-6731,-6853,-7406,-6308,-6958,-6636,-6553,-6978,-6703,-6829,-6647,-7156,-6883,-6737,-7017,-5814,-6581,-8575,-6833,-14490,-7270,-17411,-6699,-16466,-6539,-16016,-6931,-16571,-6504,-16257,-6551,-16202,-7408,-16983,-7021,-16545,-6410,-15512,-6976,-16305,-6803,-17017,-6243,-15820,-7037,-16197,-6923,-17802,-5820,-13840,-8455,-5475,-14227,-1724,-17099,-2923,-16314,-3008,-15801,-2362,-16392,-3088,-16506,-3163,-16356,-2503,-16700,-2717,-16605,-2855,-15904,-2710,-16226,-3108,-16870,-3089,-16101,-2747,-16257,-3087,-17584,-2975,-13868,-2324,-5343,-2548,-1275,-2673,-2917,-2213,-3363,-2694,-2311,-3251,-2744,-2867,-3129,-3034,-2939,-3190,-3234,-2346,-2964,-2639,-2658,-3558,-3241,-2670,-2892,-2453,-2437,-3564,-3175,-771,-779,5105,5171,7308,7655,6265,6204,6397,6288,7024,7172,6903,6586,7002,6627,6777,7308,6190,6889,6537,6465,7011,6613,6985,6631,7393,6934,7073,7072,6112,6615,8751,6859,14672,7282,17448,6652,16146,6448,15565,6899,16151,6547,15860,6591,16048,7446,17065,7064,16661,6368,15774,6857,16524,6677,16825,6071,15577,6900,16119,7040,17490,6118,13495,8696,5432,14446,1678,17366,3036,16488,3624,15834,3012,16382,3575,16465,3685,16301,2815,16708,2982,16679,3356,15952,2934,16049,3290,16352,3964,15605,3612,16222,3647,17764,4272,13865,3977,5384,3592,1580,3794,3243,3627,3670,3622,2758,4007,3130,3835,3294,3964,3065,4468,3408,3933,3234,3789,3118,4634,3643,4211,3174,4155,3176,5512,4400,2792,1730,-3702,-4499,-5940,-6691,-4265,-5094,-4381,-5215,-4918,-5746,-4217,-4871,-4402,-4981,-4479,-5525,-3732,-4968,-4118,-4924,-4300,-5349,-3422,-5021,-3876,-4886,-4087,-4860,-2790,-4254,-5025,-4196,-10898,-4415,-13419,-4007,-12198,-4121,-11995,-4413,-12471,-3808,-11937,-3920,-11792,-4583,-12284,-3776,-12085,-3107,-11421,-3583,-11226,-3081,-11157,-2768,-10580,-3914,-10424,-3197,-11040,-1715,-9822,-5144,-6189,-11154,-4236,-13029,-5134,-11598,-5507,-10949,-4921,-11142,-4999,-11180,-4883,-11184,-4366,-11090,-4548,-10887,-4818,-10708,-4866,-10534,-5253,-10272,-5179,-9894,-4633,-10029,-4773,-10382,-4977,-8674,-4668,-5292,-4651,-3928,-4629,-4465,-4312,-3994,-4459,-3528,-4570,-4400,-4272,-4601,-4482,-4035,-4627,-4334,-4080,-4498,-4045,-3835,-4204,-3526,-3695,-3646,-4045,-4101,-4856,-4628,-3338,-3235,-673,-508,28,147,-453,-639,11,0,8,-2,7,0,7,-3,11,-8,15,-9,17,-6,17,-5,13,-3,7,0,3,0,-2,0,-4,0,-4,-2,-6,0,-14,-2,-17,-4,-8,0,-7,5,-17,7,-18,10,-7,18,-2,25,-3,27,0,31,4,34,4,34,8,36,8,37,2,36,4,34,8,28,3,15,0,11,0,12,-5,8,-4,10,0,23,-4,31,-8,30,-2,30,0,26,-6,22,-6,20,-12,15,-19,10,-10,13,-14,6,-43,-13,-43,-16,-9,-12,-10,-29,-42,-40,-37,-28,-5,-21,1,-24,-8,-20,4,-18,26,-24,44,-26,66,-30,86,-37,88,-41,72,-46,50,-31,28,23,14,64,16,51,26,32,34,39,42,48,35,58,0,72,-36,69,-59,58,-98,54,-124,36,-103,12,-110,5,-173,-19,-146,-59,-4,-42,51,1,-23,-6,-30,-6,45,46,47,70,6,55,19,60,38,62,42,47,61,46,40,42,-19,22,-34,6,-35,-50,-61,-141,-37,-171,17,-163,26,-180,46,-154,80,-63,48,-4,18,20,50,47,58,53,44,61,57,85,37,80,0,86,-8,106,-95,49,-213,-8,-131,47,49,63,40,-39,-69,-74,-37,-20,63,-12,58,-14,-12,25,-31,41,11,45,76,47,167,5,261,-37,277,-83,183,-172,35,-122,-79,138,-70,266,69,124,228,0,391,-29,594,-84,702,-78,627,-8,551,-13,509,13,372,120,352,125,622,127,691,223,362,126,386,-33,915,198,958,457,456,298,500,233,1027,469,1096,426,918,160,1067,141,1220,189,1245,164,1375,297,1378,503,1299,702,1550,929,1799,855,1752,547,1830,602,1928,832,1736,796,1735,933,1961,1385,1935,1562,2105,1485,2716,1449,2948,1305,2768,1205,2716,1346,2531,1450,2470,1653,3117,2111,3370,2176,2696,1947,2925,2305,3846,2658,2425,2184,-877,1981,-2261,2623,-1645,2908,-1876,2732,-2704,2953,-2484,3116,-2120,2954,-2442,3216,-2466,3499,-2192,3234,-2392,3361,-2497,3869,-2078,3772,-1858,3915,-2066,4438,-2285,2934,-2294,-280,-2066,-1762,-1992,-1412,-2298,-1535,-2399,-1789,-2223,-1419,-2244,-1334,-2092,-1476,-1777,-1396,-2014,-1571,-2199,-1574,-1843,-1167,-1910,-1446,-2007,-1818,-1506,-1331,-2526,-2048,-5535,-4573,-7148,-5828,-6422,-5327,-5840,-5488,-5992,-6144,-6014,-6164,-6109,-6234,-6271,-6388,-6288,-6156,-6517,-6249,-6794,-6602,-6822,-6418,-6788,-6245,-6490,-6560,-6394,-6794,-7920,-6937,-10397,-7140,-11428,-6972,-11019,-6610,-11141,-6665,-11913,-7046,-11979,-7235,-11599,-7015,-11854,-6912,-12161,-7441,-12136,-7761,-12861,-7292,-13390,-7254,-12345,-7809,-12490,-7463,-13983,-6969,-10489,-8465,-2382,-11054,1272,-12247,-270,-12060,-323,-12113,502,-12486,-697,-12251,-1086,-12141,-181,-13116,-670,-13509,-1173,-12592,-443,-12811,-449,-13698,-934,-12850,-747,-13083,-873,-15036,-1161,-11478,-1047,-2669,-1407,1006,-1658,-1146,-1195,-1297,-1421,-73,-1946,-977,-1590,-1499,-1577,-1010,-1862,-1256,-1389,-962,-1692,-509,-2613,-1317,-2087,-1359,-1997,-1034,-2891,-2024,-119,-84,5651,5723,8074,8306,7156,6870,6985,7106,7312,8403,7114,8096,7173,7848,7082,7827,6761,7189,6985,7368]}"#; + +use serde::Deserialize; +use std::thread; + +#[derive(Deserialize)] +struct DeriveStruct { + buffer: Vec, +} + +fn main() { + let t = thread::spawn(|| { + let _info: DeriveStruct = serde_json::from_str(JSON).unwrap(); + }); + let _info: DeriveStruct = serde_json::from_str(JSON).unwrap(); + t.join().unwrap(); +} diff --git a/src/tools/miri/bench-cargo-miri/slice-get-unchecked/Cargo.lock b/src/tools/miri/bench-cargo-miri/slice-get-unchecked/Cargo.lock new file mode 100644 index 0000000000000..a375afaed3098 --- /dev/null +++ b/src/tools/miri/bench-cargo-miri/slice-get-unchecked/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "slice-get-unchecked" +version = "0.1.0" diff --git a/src/tools/miri/bench-cargo-miri/slice-get-unchecked/Cargo.toml b/src/tools/miri/bench-cargo-miri/slice-get-unchecked/Cargo.toml new file mode 100644 index 0000000000000..1ac2276866ff7 --- /dev/null +++ b/src/tools/miri/bench-cargo-miri/slice-get-unchecked/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "slice-get-unchecked" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/tools/miri/bench-cargo-miri/slice-get-unchecked/src/main.rs b/src/tools/miri/bench-cargo-miri/slice-get-unchecked/src/main.rs new file mode 100644 index 0000000000000..a72083bd9de31 --- /dev/null +++ b/src/tools/miri/bench-cargo-miri/slice-get-unchecked/src/main.rs @@ -0,0 +1,12 @@ +//! This is a stripped-down version of the code pattern that causes runtime blowup when printing +//! backtraces in a failed test under cargo miri test with -Zmiri-disable-isolation. +//! See https://github.com/rust-lang/miri/issues/2273 + +fn main() { + let x = vec![0u8; 4096]; + let mut i = 0; + while i < x.len() { + let _element = unsafe { *x.get_unchecked(i) }; + i += 1; + } +} diff --git a/src/tools/miri/bench-cargo-miri/unicode/Cargo.lock b/src/tools/miri/bench-cargo-miri/unicode/Cargo.lock new file mode 100644 index 0000000000000..80d013b7d6d84 --- /dev/null +++ b/src/tools/miri/bench-cargo-miri/unicode/Cargo.lock @@ -0,0 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "unicode" +version = "0.1.0" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "unicode-xid" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" diff --git a/src/tools/miri/bench-cargo-miri/unicode/Cargo.toml b/src/tools/miri/bench-cargo-miri/unicode/Cargo.toml new file mode 100644 index 0000000000000..7e8708b03f19c --- /dev/null +++ b/src/tools/miri/bench-cargo-miri/unicode/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "unicode" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +unicode-xid = "0.2.3" diff --git a/src/tools/miri/bench-cargo-miri/unicode/src/main.rs b/src/tools/miri/bench-cargo-miri/unicode/src/main.rs new file mode 100644 index 0000000000000..3cf25ba9cf6c2 --- /dev/null +++ b/src/tools/miri/bench-cargo-miri/unicode/src/main.rs @@ -0,0 +1,20 @@ +//! Extracted from the unicode-xid exhaustive test all_valid_chars_do_not_panic_for_is_xid_continue + +use unicode_xid::UnicodeXID; + +/// A `char` in Rust is a Unicode Scalar Value +/// +/// See: http://www.unicode.org/glossary/#unicode_scalar_value +fn all_valid_chars() -> impl Iterator { + (0u32..=0xD7FF).chain(0xE000u32..=0x10FFFF).map(|u| { + core::convert::TryFrom::try_from(u) + .expect("The selected range should be infallible if the docs match impl") + }) +} + +fn main() { + // Take only the first few chars because we don't want to wait all day + for c in all_valid_chars().take(1_500) { + let _ = UnicodeXID::is_xid_continue(c); + } +} diff --git a/src/tools/miri/build.rs b/src/tools/miri/build.rs new file mode 100644 index 0000000000000..37c626baab58a --- /dev/null +++ b/src/tools/miri/build.rs @@ -0,0 +1,8 @@ +fn main() { + // Don't rebuild miri when nothing changed. + println!("cargo:rerun-if-changed=build.rs"); + // Re-export the TARGET environment variable so it can + // be accessed by miri. + let target = std::env::var("TARGET").unwrap(); + println!("cargo:rustc-env=TARGET={}", target); +} diff --git a/src/tools/miri/cargo-miri/Cargo.lock b/src/tools/miri/cargo-miri/Cargo.lock new file mode 100644 index 0000000000000..95c2bda505c5b --- /dev/null +++ b/src/tools/miri/cargo-miri/Cargo.lock @@ -0,0 +1,554 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anyhow" +version = "1.0.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b26702f315f53b6071259e15dd9d64528213b44d61de1ec926eca7715d62203" + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "camino" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "869119e97797867fd90f5e22af7d0bd274bd4635ebb9eb68c04f3f513ae6c412" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-miri" +version = "0.1.0" +dependencies = [ + "cargo_metadata", + "directories", + "rustc-workspace-hack", + "rustc_version", + "serde", + "serde_json", + "vergen", +] + +[[package]] +name = "cargo-platform" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3abb7553d5b9b8421c6de7cb02606ff15e0c6eea7d8eadd75ef013fd636bec36" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", +] + +[[package]] +name = "cc" +version = "1.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" +dependencies = [ + "jobserver", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time", + "winapi", +] + +[[package]] +name = "directories" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e69600ff1703123957937708eb27f7a564e48885c537782722ed0ba3189ce1d7" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "enum-iterator" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eeac5c5edb79e4e39fe8439ef35207780a11f69c52cbe424ce3dfad4cb78de6" +dependencies = [ + "enum-iterator-derive", +] + +[[package]] +name = "enum-iterator-derive" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c134c37760b27a871ba422106eedbb8247da973a09e82558bf26d619c882b159" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "getrandom" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getset" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e45727250e75cc04ff2846a66397da8ef2b3db8e40e0cef4df67950a07621eb9" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "git2" +version = "0.13.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29229cc1b24c0e6062f6e742aa3e256492a5323365e5ed3413599f8a5eff7d6" +dependencies = [ + "bitflags", + "libc", + "libgit2-sys", + "log", + "url", +] + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "itoa" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" + +[[package]] +name = "jobserver" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" +dependencies = [ + "libc", +] + +[[package]] +name = "libc" +version = "0.2.112" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" + +[[package]] +name = "libgit2-sys" +version = "0.12.26+1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e1c899248e606fbfe68dcb31d8b0176ebab833b103824af31bddf4b7457494" +dependencies = [ + "cc", + "libc", + "libz-sys", + "pkg-config", +] + +[[package]] +name = "libz-sys" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de5435b8549c16d423ed0c03dbaafe57cf6c3344744f1242520d59c9d8ecec66" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pkg-config" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c278e965f1d8cf32d6e0e96de3d3e79712178ae67986d9cf9151f51e95aac89b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +dependencies = [ + "getrandom", + "redox_syscall", +] + +[[package]] +name = "rustc-workspace-hack" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc71d2faa173b74b232dedc235e3ee1696581bb132fc116fa3626d6151a1a8fb" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" + +[[package]] +name = "ryu" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" + +[[package]] +name = "semver" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2333e6df6d6598f2b1974829f853c2b4c5f4a6e503c10af918081aa6f8564e1" +dependencies = [ + "serde", +] + +[[package]] +name = "serde" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc855a42c7967b7c369eb5860f7164ef1f6f81c20c7cc1141f2a604e18723b03" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f2122636b9fe3b81f1cb25099fcf2d3f542cdb1d45940d56c713158884a05da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "syn" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "tinyvec" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "unicode-bidi" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" + +[[package]] +name = "unicode-ident" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7" + +[[package]] +name = "unicode-normalization" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "vergen" +version = "5.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cf88d94e969e7956d924ba70741316796177fa0c79a2c9f4ab04998d96e966e" +dependencies = [ + "anyhow", + "cfg-if", + "chrono", + "enum-iterator", + "getset", + "git2", + "rustversion", + "thiserror", +] + +[[package]] +name = "version_check" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/src/tools/miri/cargo-miri/Cargo.toml b/src/tools/miri/cargo-miri/Cargo.toml new file mode 100644 index 0000000000000..9ac170c5b5377 --- /dev/null +++ b/src/tools/miri/cargo-miri/Cargo.toml @@ -0,0 +1,32 @@ +[package] +authors = ["Miri Team"] +description = "An experimental interpreter for Rust MIR (cargo wrapper)." +license = "MIT OR Apache-2.0" +name = "cargo-miri" +repository = "https://github.com/rust-lang/miri" +version = "0.1.0" +edition = "2021" + +[[bin]] +name = "cargo-miri" +path = "src/main.rs" +test = false # we have no unit tests +doctest = false # and no doc tests + +[dependencies] +directories = "3" +rustc_version = "0.4" +serde_json = "1.0.40" +cargo_metadata = "0.15.0" + +# A noop dependency that changes in the Rust repository, it's a bit of a hack. +# See the `src/tools/rustc-workspace-hack/README.md` file in `rust-lang/rust` +# for more information. +rustc-workspace-hack = "1.0.0" + +# Enable some feature flags that dev-dependencies need but dependencies +# do not. This makes `./miri install` after `./miri build` faster. +serde = { version = "*", features = ["derive"] } + +[build-dependencies] +vergen = { version = "5", default_features = false, features = ["git"] } diff --git a/src/tools/miri/cargo-miri/build.rs b/src/tools/miri/cargo-miri/build.rs new file mode 100644 index 0000000000000..ebd8e7003d5f7 --- /dev/null +++ b/src/tools/miri/cargo-miri/build.rs @@ -0,0 +1,11 @@ +use vergen::vergen; + +fn main() { + // Don't rebuild miri when nothing changed. + println!("cargo:rerun-if-changed=build.rs"); + // vergen + let mut gen_config = vergen::Config::default(); + *gen_config.git_mut().sha_kind_mut() = vergen::ShaKind::Short; + *gen_config.git_mut().commit_timestamp_kind_mut() = vergen::TimestampKind::DateOnly; + vergen(gen_config).ok(); // Ignore failure (in case we are built outside a git repo) +} diff --git a/src/tools/miri/cargo-miri/miri b/src/tools/miri/cargo-miri/miri new file mode 100755 index 0000000000000..cf3ad06788ab1 --- /dev/null +++ b/src/tools/miri/cargo-miri/miri @@ -0,0 +1,4 @@ +#!/bin/sh +# RA invokes `./miri cargo ...` for each workspace, so we need to forward that to the main `miri` +# script. See . +exec "$(dirname "$0")"/../miri "$@" diff --git a/src/tools/miri/cargo-miri/src/arg.rs b/src/tools/miri/cargo-miri/src/arg.rs new file mode 100644 index 0000000000000..e8bac4625f710 --- /dev/null +++ b/src/tools/miri/cargo-miri/src/arg.rs @@ -0,0 +1,134 @@ +//! Utilities for dealing with argument flags + +use std::borrow::Cow; +use std::env; + +/// Determines whether a `--flag` is present. +pub fn has_arg_flag(name: &str) -> bool { + num_arg_flag(name) > 0 +} + +/// Determines how many times a `--flag` is present. +pub fn num_arg_flag(name: &str) -> usize { + env::args().take_while(|val| val != "--").filter(|val| val == name).count() +} + +/// Yields all values of command line flag `name` as `Ok(arg)`, and all other arguments except +/// the flag as `Err(arg)`. (The flag `name` itself is not yielded at all, only its values are.) +pub struct ArgSplitFlagValue<'a, I> { + args: Option, + name: &'a str, +} + +impl<'a, I: Iterator> ArgSplitFlagValue<'a, I> { + fn new(args: I, name: &'a str) -> Self { + Self { args: Some(args), name } + } +} + +impl<'s, I: Iterator>> Iterator for ArgSplitFlagValue<'_, I> { + // If the original iterator was all `Owned`, then we will only ever yield `Owned` + // (so `into_owned()` is cheap). + type Item = Result, Cow<'s, str>>; + + fn next(&mut self) -> Option { + let Some(args) = self.args.as_mut() else { + // We already canceled this iterator. + return None; + }; + let arg = args.next()?; + if arg == "--" { + // Stop searching at `--`. + self.args = None; + return None; + } + // These branches cannot be merged if we want to avoid the allocation in the `Borrowed` branch. + match &arg { + Cow::Borrowed(arg) => + if let Some(suffix) = arg.strip_prefix(self.name) { + // Strip leading `name`. + if suffix.is_empty() { + // This argument is exactly `name`; the next one is the value. + return args.next().map(Ok); + } else if let Some(suffix) = suffix.strip_prefix('=') { + // This argument is `name=value`; get the value. + return Some(Ok(Cow::Borrowed(suffix))); + } + }, + Cow::Owned(arg) => + if let Some(suffix) = arg.strip_prefix(self.name) { + // Strip leading `name`. + if suffix.is_empty() { + // This argument is exactly `name`; the next one is the value. + return args.next().map(Ok); + } else if let Some(suffix) = suffix.strip_prefix('=') { + // This argument is `name=value`; get the value. We need to do an allocation + // here as a `String` cannot be subsliced (what would the lifetime be?). + return Some(Ok(Cow::Owned(suffix.to_owned()))); + } + }, + } + Some(Err(arg)) + } +} + +impl<'a, I: Iterator + 'a> ArgSplitFlagValue<'a, I> { + pub fn from_string_iter( + args: I, + name: &'a str, + ) -> impl Iterator> + 'a { + ArgSplitFlagValue::new(args.map(Cow::Owned), name).map(|x| { + match x { + Ok(Cow::Owned(s)) => Ok(s), + Err(Cow::Owned(s)) => Err(s), + _ => panic!("iterator converted owned to borrowed"), + } + }) + } +} + +impl<'x: 'a, 'a, I: Iterator + 'a> ArgSplitFlagValue<'a, I> { + pub fn from_str_iter( + args: I, + name: &'a str, + ) -> impl Iterator> + 'a { + ArgSplitFlagValue::new(args.map(Cow::Borrowed), name).map(|x| { + match x { + Ok(Cow::Borrowed(s)) => Ok(s), + Err(Cow::Borrowed(s)) => Err(s), + _ => panic!("iterator converted borrowed to owned"), + } + }) + } +} + +/// Yields all values of command line flag `name`. +pub struct ArgFlagValueIter; + +impl ArgFlagValueIter { + pub fn from_string_iter<'a, I: Iterator + 'a>( + args: I, + name: &'a str, + ) -> impl Iterator + 'a { + ArgSplitFlagValue::from_string_iter(args, name).filter_map(Result::ok) + } +} + +impl ArgFlagValueIter { + pub fn from_str_iter<'x: 'a, 'a, I: Iterator + 'a>( + args: I, + name: &'a str, + ) -> impl Iterator + 'a { + ArgSplitFlagValue::from_str_iter(args, name).filter_map(Result::ok) + } +} + +/// Gets the values of a `--flag`. +pub fn get_arg_flag_values(name: &str) -> impl Iterator + '_ { + ArgFlagValueIter::from_string_iter(env::args(), name) +} + +/// Gets the value of a `--flag`. +pub fn get_arg_flag_value(name: &str) -> Option { + get_arg_flag_values(name).next() +} diff --git a/src/tools/miri/cargo-miri/src/main.rs b/src/tools/miri/cargo-miri/src/main.rs new file mode 100644 index 0000000000000..331c4c9c2b0e7 --- /dev/null +++ b/src/tools/miri/cargo-miri/src/main.rs @@ -0,0 +1,97 @@ +#![allow(clippy::useless_format, clippy::derive_partial_eq_without_eq, rustc::internal)] + +#[macro_use] +mod util; + +mod arg; +mod phases; +mod setup; +mod version; + +use std::{env, iter}; + +use crate::phases::*; + +fn main() { + // Rustc does not support non-UTF-8 arguments so we make no attempt either. + // (We do support non-UTF-8 environment variables though.) + let mut args = std::env::args(); + // Skip binary name. + args.next().unwrap(); + + // Dispatch to `cargo-miri` phase. Here is a rough idea of "who calls who". + // + // Initially, we are invoked as `cargo-miri miri run/test`. We first run the setup phase: + // - We call `xargo`, and set `RUSTC` back to us, together with `MIRI_CALLED_FROM_XARGO`, + // so that xargo's rustc invocations end up in `phase_rustc` with `RustcPhase::Setup`. + // There we then call the Miri driver with `MIRI_BE_RUSTC` to perform the actual build. + // + // Then we call `cargo run/test`, exactly forwarding all user flags, plus some configuration so + // that we control every binary invoked by cargo: + // - We set RUSTC_WRAPPER to ourselves, so for (almost) all rustc invocations, we end up in + // `phase_rustc` with `RustcPhase::Build`. This will in turn either determine that a + // dependency needs to be built (for which it invokes the Miri driver with `MIRI_BE_RUSTC`), + // or determine that this is a binary Miri should run, in which case we generate a JSON file + // with all the information needed to build and run this crate. + // (We don't run it yet since cargo thinks this is a build step, not a run step -- running the + // binary here would lead to a bad user experience.) + // - We set RUSTC to the Miri driver and also set `MIRI_BE_RUSTC`, so that gets called by build + // scripts (and cargo uses it for the version query). + // - We set `target.*.runner` to `cargo-miri runner`, which ends up calling `phase_runner` for + // `RunnerPhase::Cargo`. This parses the JSON file written in `phase_rustc` and then invokes + // the actual Miri driver for interpretation. + // - We set RUSTDOC to ourselves, which ends up in `phase_rustdoc`. There we call regular + // rustdoc with some extra flags, and we set `MIRI_CALLED_FROM_RUSTDOC` to recognize this + // phase in our recursive invocations: + // - We set the `--test-builder` flag of rustdoc to ourselves, which ends up in `phase_rustc` + // with `RustcPhase::Rustdoc`. There we perform a check-build (needed to get the expected + // build failures for `compile_fail` doctests) and then store a JSON file with the + // information needed to run this test. + // - We also set `--runtool` to ourselves, which ends up in `phase_runner` with + // `RunnerPhase::Rustdoc`. There we parse the JSON file written in `phase_rustc` and invoke + // the Miri driver for interpretation. + + // Dispatch running as part of sysroot compilation. + if env::var_os("MIRI_CALLED_FROM_XARGO").is_some() { + phase_rustc(args, RustcPhase::Setup); + return; + } + + // The way rustdoc invokes rustc is indistuingishable from the way cargo invokes rustdoc by the + // arguments alone. `phase_cargo_rustdoc` sets this environment variable to let us disambiguate. + if env::var_os("MIRI_CALLED_FROM_RUSTDOC").is_some() { + // ...however, we then also see this variable when rustdoc invokes us as the testrunner! + // The runner is invoked as `$runtool ($runtool-arg)* output_file`; + // since we don't specify any runtool-args, and rustdoc supplies multiple arguments to + // the test-builder unconditionally, we can just check the number of remaining arguments: + if args.len() == 1 { + phase_runner(args, RunnerPhase::Rustdoc); + } else { + phase_rustc(args, RustcPhase::Rustdoc); + } + + return; + } + + let Some(first) = args.next() else { + show_error!( + "`cargo-miri` called without first argument; please only invoke this binary through `cargo miri`" + ) + }; + match first.as_str() { + "miri" => phase_cargo_miri(args), + "runner" => phase_runner(args, RunnerPhase::Cargo), + arg if arg == env::var("RUSTC").unwrap() => { + // If the first arg is equal to the RUSTC env ariable (which should be set at this + // point), then we need to behave as rustc. This is the somewhat counter-intuitive + // behavior of having both RUSTC and RUSTC_WRAPPER set + // (see https://github.com/rust-lang/cargo/issues/10886). + phase_rustc(args, RustcPhase::Build) + } + _ => { + // Everything else must be rustdoc. But we need to get `first` "back onto the iterator", + // it is some part of the rustdoc invocation. + phase_rustdoc(iter::once(first).chain(args)); + } + } +} diff --git a/src/tools/miri/cargo-miri/src/phases.rs b/src/tools/miri/cargo-miri/src/phases.rs new file mode 100644 index 0000000000000..93eb3cb174659 --- /dev/null +++ b/src/tools/miri/cargo-miri/src/phases.rs @@ -0,0 +1,601 @@ +//! Implements the various phases of `cargo miri run/test`. + +use std::env; +use std::fmt::Write as _; +use std::fs::{self, File}; +use std::io::BufReader; +use std::path::PathBuf; +use std::process::Command; + +use crate::{setup::*, util::*}; + +const CARGO_MIRI_HELP: &str = r#"Runs binary crates and tests in Miri + +Usage: + cargo miri [subcommand] [...] [--] [...] + +Subcommands: + run, r Run binaries + test, t Run tests + nextest Run tests with nextest (requires cargo-nextest installed) + setup Only perform automatic setup, but without asking questions (for getting a proper libstd) + +The cargo options are exactly the same as for `cargo run` and `cargo test`, respectively. + +Examples: + cargo miri run + cargo miri test -- test-suite-filter + + cargo miri setup --print sysroot + This will print the path to the generated sysroot (and nothing else) on stdout. + stderr will still contain progress information about how the build is doing. + +"#; + +fn show_help() { + println!("{}", CARGO_MIRI_HELP); +} + +fn show_version() { + let mut version = format!("miri {}", env!("CARGO_PKG_VERSION")); + // Only use `option_env` on vergen variables to ensure the build succeeds + // when vergen failed to find the git info. + if let Some(sha) = option_env!("VERGEN_GIT_SHA_SHORT") { + // This `unwrap` can never fail because if VERGEN_GIT_SHA_SHORT exists, then so does + // VERGEN_GIT_COMMIT_DATE. + #[allow(clippy::option_env_unwrap)] + write!(&mut version, " ({} {})", sha, option_env!("VERGEN_GIT_COMMIT_DATE").unwrap()) + .unwrap(); + } + println!("{}", version); +} + +fn forward_patched_extern_arg(args: &mut impl Iterator, cmd: &mut Command) { + cmd.arg("--extern"); // always forward flag, but adjust filename: + let path = args.next().expect("`--extern` should be followed by a filename"); + if let Some(lib) = path.strip_suffix(".rlib") { + // If this is an rlib, make it an rmeta. + cmd.arg(format!("{}.rmeta", lib)); + } else { + // Some other extern file (e.g. a `.so`). Forward unchanged. + cmd.arg(path); + } +} + +pub fn phase_cargo_miri(mut args: impl Iterator) { + // Check for version and help flags even when invoked as `cargo-miri`. + if has_arg_flag("--help") || has_arg_flag("-h") { + show_help(); + return; + } + if has_arg_flag("--version") || has_arg_flag("-V") { + show_version(); + return; + } + + // Require a subcommand before any flags. + // We cannot know which of those flags take arguments and which do not, + // so we cannot detect subcommands later. + let Some(subcommand) = args.next() else { + show_error!("`cargo miri` needs to be called with a subcommand (`run`, `test`)"); + }; + let subcommand = match &*subcommand { + "setup" => MiriCommand::Setup, + "test" | "t" | "run" | "r" | "nextest" => MiriCommand::Forward(subcommand), + _ => + show_error!( + "`cargo miri` supports the following subcommands: `run`, `test`, `nextest`, and `setup`." + ), + }; + let verbose = num_arg_flag("-v"); + + // Determine the involved architectures. + let host = version_info().host; + let target = get_arg_flag_value("--target"); + let target = target.as_ref().unwrap_or(&host); + + // We always setup. + setup(&subcommand, &host, target); + + // Invoke actual cargo for the job, but with different flags. + // We re-use `cargo test` and `cargo run`, which makes target and binary handling very easy but + // requires some extra work to make the build check-only (see all the `--emit` hacks below). + // describes an alternative + // approach that uses `cargo check`, making that part easier but target and binary handling + // harder. + let cargo_miri_path = std::env::current_exe() + .expect("current executable path invalid") + .into_os_string() + .into_string() + .expect("current executable path is not valid UTF-8"); + let cargo_cmd = match subcommand { + MiriCommand::Forward(s) => s, + MiriCommand::Setup => return, // `cargo miri setup` stops here. + }; + let metadata = get_cargo_metadata(); + let mut cmd = cargo(); + cmd.arg(cargo_cmd); + + // Forward all arguments before `--` other than `--target-dir` and its value to Cargo. + // (We want to *change* the target-dir value, so we must not forward it.) + let mut target_dir = None; + for arg in ArgSplitFlagValue::from_string_iter(&mut args, "--target-dir") { + match arg { + Ok(value) => { + if target_dir.is_some() { + show_error!("`--target-dir` is provided more than once"); + } + target_dir = Some(value.into()); + } + Err(arg) => { + cmd.arg(arg); + } + } + } + // Detect the target directory if it's not specified via `--target-dir`. + // (`cargo metadata` does not support `--target-dir`, that's why we have to handle this ourselves.) + let target_dir = target_dir.get_or_insert_with(|| metadata.target_directory.clone()); + // Set `--target-dir` to `miri` inside the original target directory. + target_dir.push("miri"); + cmd.arg("--target-dir").arg(target_dir); + + // Make sure the build target is explicitly set. + // This is needed to make the `target.runner` settings do something, + // and it later helps us detect which crates are proc-macro/build-script + // (host crates) and which crates are needed for the program itself. + if get_arg_flag_value("--target").is_none() { + // No target given. Explicitly pick the host. + cmd.arg("--target"); + cmd.arg(&host); + } + + // Set ourselves as runner for al binaries invoked by cargo. + // We use `all()` since `true` is not a thing in cfg-lang, but the empty conjunction is. :) + let cargo_miri_path_for_toml = escape_for_toml(&cargo_miri_path); + cmd.arg("--config") + .arg(format!("target.'cfg(all())'.runner=[{cargo_miri_path_for_toml}, 'runner']")); + + // Forward all further arguments after `--` to cargo. + cmd.arg("--").args(args); + + // 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`.) + if env::var_os("RUSTC_WRAPPER").is_some() { + println!( + "WARNING: Ignoring `RUSTC_WRAPPER` environment variable, Miri does not support wrapping." + ); + } + cmd.env("RUSTC_WRAPPER", &cargo_miri_path); + // We are going to invoke `MIRI` for everything, not `RUSTC`. + if env::var_os("RUSTC").is_some() && env::var_os("MIRI").is_none() { + println!( + "WARNING: Ignoring `RUSTC` environment variable; set `MIRI` if you want to control the binary used as the driver." + ); + } + // Build scripts (and also cargo: https://github.com/rust-lang/cargo/issues/10885) will invoke + // `rustc` even when `RUSTC_WRAPPER` is set. 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. + // + // 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 + // there would be a collision with other invocations of cargo-miri (as rustdoc or as runner). We + // explicitly do this even if RUSTC_STAGE is set, since for these builds we do *not* want the + // 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! + + // Set rustdoc to us as well, so we can run doctests. + cmd.env("RUSTDOC", &cargo_miri_path); + + cmd.env("MIRI_LOCAL_CRATES", local_crates(&metadata)); + if verbose > 0 { + cmd.env("MIRI_VERBOSE", verbose.to_string()); // This makes the other phases verbose. + } + + // Run cargo. + debug_cmd("[cargo-miri miri]", verbose, &cmd); + exec(cmd) +} + +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum RustcPhase { + /// `rustc` called via `xargo` for sysroot build. + Setup, + /// `rustc` called by `cargo` for regular build. + Build, + /// `rustc` called by `rustdoc` for doctest. + Rustdoc, +} + +pub fn phase_rustc(mut args: impl Iterator, phase: RustcPhase) { + /// Determines if we are being invoked (as rustc) to build a crate for + /// the "target" architecture, in contrast to the "host" architecture. + /// Host crates are for build scripts and proc macros and still need to + /// be built like normal; target crates need to be built for or interpreted + /// by Miri. + /// + /// Currently, we detect this by checking for "--target=", which is + /// never set for host crates. This matches what rustc bootstrap does, + /// which hopefully makes it "reliable enough". This relies on us always + /// invoking cargo itself with `--target`, which `in_cargo_miri` ensures. + fn is_target_crate() -> bool { + get_arg_flag_value("--target").is_some() + } + + /// Returns whether or not Cargo invoked the wrapper (this binary) to compile + /// the final, binary crate (either a test for 'cargo test', or a binary for 'cargo run') + /// Cargo does not give us this information directly, so we need to check + /// various command-line flags. + fn is_runnable_crate() -> bool { + let is_bin = get_arg_flag_value("--crate-type").as_deref().unwrap_or("bin") == "bin"; + let is_test = has_arg_flag("--test"); + is_bin || is_test + } + + fn out_filename(prefix: &str, suffix: &str) -> PathBuf { + if let Some(out_dir) = get_arg_flag_value("--out-dir") { + let mut path = PathBuf::from(out_dir); + path.push(format!( + "{}{}{}{}", + prefix, + get_arg_flag_value("--crate-name").unwrap(), + // This is technically a `-C` flag but the prefix seems unique enough... + // (and cargo passes this before the filename so it should be unique) + get_arg_flag_value("extra-filename").unwrap_or_default(), + suffix, + )); + path + } else { + let out_file = get_arg_flag_value("-o").unwrap(); + PathBuf::from(out_file) + } + } + + // phase_cargo_miri set `MIRI_BE_RUSTC` for when build scripts directly invoke the driver; + // however, if we get called back by cargo here, we'll carefully compute the right flags + // ourselves, so we first un-do what the earlier phase did. + env::remove_var("MIRI_BE_RUSTC"); + + let verbose = std::env::var("MIRI_VERBOSE") + .map_or(0, |verbose| verbose.parse().expect("verbosity flag must be an integer")); + let target_crate = is_target_crate(); + // Determine whether this is cargo/xargo invoking rustc to get some infos. + let info_query = get_arg_flag_value("--print").is_some() || has_arg_flag("-vV"); + + let store_json = |info: CrateRunInfo| { + // Create a stub .d file to stop Cargo from "rebuilding" the crate: + // https://github.com/rust-lang/miri/issues/1724#issuecomment-787115693 + // As we store a JSON file instead of building the crate here, an empty file is fine. + let dep_info_name = out_filename("", ".d"); + if verbose > 0 { + eprintln!("[cargo-miri rustc] writing stub dep-info to `{}`", dep_info_name.display()); + } + File::create(dep_info_name).expect("failed to create fake .d file"); + + let filename = out_filename("", ""); + if verbose > 0 { + eprintln!("[cargo-miri rustc] writing run info to `{}`", filename.display()); + } + info.store(&filename); + // For Windows, do the same thing again with `.exe` appended to the filename. + // (Need to do this here as cargo moves that "binary" to a different place before running it.) + info.store(&out_filename("", ".exe")); + }; + + let runnable_crate = !info_query && is_runnable_crate(); + + if runnable_crate && target_crate { + assert!( + phase != RustcPhase::Setup, + "there should be no interpretation during sysroot build" + ); + let inside_rustdoc = phase == RustcPhase::Rustdoc; + // This is the binary or test crate that we want to interpret under Miri. + // But we cannot run it here, as cargo invoked us as a compiler -- our stdin and stdout are not + // like we want them. + // Instead of compiling, we write JSON into the output file with all the relevant command-line flags + // and environment variables; this is used when cargo calls us again in the CARGO_TARGET_RUNNER phase. + let env = CrateRunEnv::collect(args, inside_rustdoc); + + store_json(CrateRunInfo::RunWith(env.clone())); + + // Rustdoc expects us to exit with an error code if the test is marked as `compile_fail`, + // just creating the JSON file is not enough: we need to detect syntax errors, + // so we need to run Miri with `MIRI_BE_RUSTC` for a check-only build. + if inside_rustdoc { + let mut cmd = miri(); + + // Ensure --emit argument for a check-only build is present. + if let Some(val) = + ArgFlagValueIter::from_str_iter(env.args.iter().map(|s| s as &str), "--emit").next() + { + // For `no_run` tests, rustdoc passes a `--emit` flag; make sure it has the right shape. + assert_eq!(val, "metadata"); + } else { + // For all other kinds of tests, we can just add our flag. + cmd.arg("--emit=metadata"); + } + + // Alter the `-o` parameter so that it does not overwrite the JSON file we stored above. + let mut args = env.args; + for i in 0..args.len() { + if args[i] == "-o" { + args[i + 1].push_str(".miri"); + } + } + + cmd.args(&args); + cmd.env("MIRI_BE_RUSTC", "target"); + + if verbose > 0 { + eprintln!( + "[cargo-miri rustc inside rustdoc] captured input:\n{}", + std::str::from_utf8(&env.stdin).unwrap() + ); + eprintln!("[cargo-miri rustc inside rustdoc] going to run:\n{:?}", cmd); + } + + exec_with_pipe(cmd, &env.stdin, format!("{}.stdin", out_filename("", "").display())); + } + + return; + } + + if runnable_crate && get_arg_flag_values("--extern").any(|krate| krate == "proc_macro") { + // This is a "runnable" `proc-macro` crate (unit tests). We do not support + // interpreting that under Miri now, so we write a JSON file to (display a + // helpful message and) skip it in the runner phase. + store_json(CrateRunInfo::SkipProcMacroTest); + return; + } + + let mut cmd = miri(); + let mut emit_link_hack = false; + // 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 !info_query && target_crate { + // Forward arguments, but remove "link" from "--emit" to make this a check-only build. + let emit_flag = "--emit"; + while let Some(arg) = args.next() { + if let Some(val) = arg.strip_prefix(emit_flag) { + // Patch this argument. First, extract its value. + let val = + val.strip_prefix('=').expect("`cargo` should pass `--emit=X` as one argument"); + let mut val: Vec<_> = val.split(',').collect(); + // Now make sure "link" is not in there, but "metadata" is. + if let Some(i) = val.iter().position(|&s| s == "link") { + emit_link_hack = true; + val.remove(i); + if !val.iter().any(|&s| s == "metadata") { + val.push("metadata"); + } + } + cmd.arg(format!("{}={}", emit_flag, val.join(","))); + } else if arg == "--extern" { + // Patch `--extern` filenames, since Cargo sometimes passes stub `.rlib` files: + // https://github.com/rust-lang/miri/issues/1705 + forward_patched_extern_arg(&mut args, &mut cmd); + } else { + cmd.arg(arg); + } + } + + // During setup, patch the panic runtime for `libpanic_abort` (mirroring what bootstrap usually does). + if phase == RustcPhase::Setup + && get_arg_flag_value("--crate-name").as_deref() == Some("panic_abort") + { + cmd.arg("-C").arg("panic=abort"); + } + } else { + // For host crates (but not when we are just printing some info), + // we might still have to set the sysroot. + if !info_query { + // When we're running `cargo-miri` from `x.py` we need to pass the sysroot explicitly + // due to bootstrap complications. + if let Some(sysroot) = std::env::var_os("MIRI_HOST_SYSROOT") { + cmd.arg("--sysroot").arg(sysroot); + } + } + + // For host crates or when we are printing, just forward everything. + cmd.args(args); + } + + // We want to compile, not interpret. We still use Miri to make sure the compiler version etc + // are the exact same as what is used for interpretation. + // MIRI_DEFAULT_ARGS should not be used to build host crates, hence setting "target" or "host" + // as the value here to help Miri differentiate them. + cmd.env("MIRI_BE_RUSTC", if target_crate { "target" } else { "host" }); + + // Run it. + if verbose > 0 { + eprintln!( + "[cargo-miri rustc] target_crate={target_crate} runnable_crate={runnable_crate} info_query={info_query}" + ); + } + + // Create a stub .rlib file if "link" was requested by cargo. + // This is necessary to prevent cargo from doing rebuilds all the time. + if emit_link_hack { + // Some platforms prepend "lib", some do not... let's just create both files. + File::create(out_filename("lib", ".rlib")).expect("failed to create fake .rlib file"); + File::create(out_filename("", ".rlib")).expect("failed to create fake .rlib file"); + // Just in case this is a cdylib or staticlib, also create those fake files. + File::create(out_filename("lib", ".so")).expect("failed to create fake .so file"); + File::create(out_filename("lib", ".a")).expect("failed to create fake .a file"); + File::create(out_filename("lib", ".dylib")).expect("failed to create fake .dylib file"); + File::create(out_filename("", ".dll")).expect("failed to create fake .dll file"); + File::create(out_filename("", ".lib")).expect("failed to create fake .lib file"); + } + + debug_cmd("[cargo-miri rustc]", verbose, &cmd); + exec(cmd); +} + +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum RunnerPhase { + /// `cargo` is running a binary + Cargo, + /// `rustdoc` is running a binary + Rustdoc, +} + +pub fn phase_runner(mut binary_args: impl Iterator, phase: RunnerPhase) { + // phase_cargo_miri set `MIRI_BE_RUSTC` for when build scripts directly invoke the driver; + // however, if we get called back by cargo here, we'll carefully compute the right flags + // ourselves, so we first un-do what the earlier phase did. + env::remove_var("MIRI_BE_RUSTC"); + + let verbose = std::env::var("MIRI_VERBOSE") + .map_or(0, |verbose| verbose.parse().expect("verbosity flag must be an integer")); + + let binary = binary_args.next().unwrap(); + let file = File::open(&binary) + .unwrap_or_else(|_| show_error!( + "file {:?} not found or `cargo-miri` invoked incorrectly; please only invoke this binary through `cargo miri`", binary + )); + let file = BufReader::new(file); + + let info = serde_json::from_reader(file).unwrap_or_else(|_| { + show_error!("file {:?} contains outdated or invalid JSON; try `cargo clean`", binary) + }); + let info = match info { + CrateRunInfo::RunWith(info) => info, + CrateRunInfo::SkipProcMacroTest => { + eprintln!( + "Running unit tests of `proc-macro` crates is not currently supported by Miri." + ); + return; + } + }; + + let mut cmd = miri(); + + // Set missing env vars. We prefer build-time env vars over run-time ones; see + // for the kind of issue that fixes. + for (name, val) in info.env { + if let Some(old_val) = env::var_os(&name) { + if old_val == val { + // This one did not actually change, no need to re-set it. + // (This keeps the `debug_cmd` below more manageable.) + continue; + } else if verbose > 0 { + eprintln!( + "[cargo-miri runner] Overwriting run-time env var {:?}={:?} with build-time value {:?}", + name, old_val, val + ); + } + } + cmd.env(name, val); + } + + // 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 + // `.rmeta`. + // We also need to remove `--error-format` as cargo specifies that to be JSON, + // but when we run here, cargo does not interpret the JSON any more. `--json` + // then also nees to be dropped. + let mut args = info.args.into_iter(); + let error_format_flag = "--error-format"; + let json_flag = "--json"; + while let Some(arg) = args.next() { + if arg == "--extern" { + forward_patched_extern_arg(&mut args, &mut cmd); + } else if let Some(suffix) = arg.strip_prefix(error_format_flag) { + assert!(suffix.starts_with('=')); + // Drop this argument. + } else if let Some(suffix) = arg.strip_prefix(json_flag) { + assert!(suffix.starts_with('=')); + // Drop this argument. + } else { + cmd.arg(arg); + } + } + // Respect `MIRIFLAGS`. + if let Ok(a) = env::var("MIRIFLAGS") { + // This code is taken from `RUSTFLAGS` handling in cargo. + let args = a.split(' ').map(str::trim).filter(|s| !s.is_empty()).map(str::to_string); + cmd.args(args); + } + + // Then pass binary arguments. + cmd.arg("--"); + cmd.args(binary_args); + + // Make sure we use the build-time working directory for interpreting Miri/rustc arguments. + // But then we need to switch to the run-time one, which we instruct Miri do do by setting `MIRI_CWD`. + cmd.current_dir(info.current_dir); + cmd.env("MIRI_CWD", env::current_dir().unwrap()); + + // Run it. + debug_cmd("[cargo-miri runner]", verbose, &cmd); + match phase { + RunnerPhase::Rustdoc => exec_with_pipe(cmd, &info.stdin, format!("{}.stdin", binary)), + RunnerPhase::Cargo => exec(cmd), + } +} + +pub fn phase_rustdoc(mut args: impl Iterator) { + let verbose = std::env::var("MIRI_VERBOSE") + .map_or(0, |verbose| verbose.parse().expect("verbosity flag must be an integer")); + + // phase_cargo_miri sets the RUSTDOC env var to ourselves, so we can't use that here; + // just default to a straight-forward invocation for now: + let mut cmd = Command::new("rustdoc"); + + let extern_flag = "--extern"; + let runtool_flag = "--runtool"; + while let Some(arg) = args.next() { + if arg == extern_flag { + // Patch --extern arguments to use *.rmeta files, since phase_cargo_rustc only creates stub *.rlib files. + forward_patched_extern_arg(&mut args, &mut cmd); + } else if arg == runtool_flag { + // An existing --runtool flag indicates cargo is running in cross-target mode, which we don't support. + // Note that this is only passed when cargo is run with the unstable -Zdoctest-xcompile flag; + // otherwise, we won't be called as rustdoc at all. + show_error!("cross-interpreting doctests is not currently supported by Miri."); + } else { + cmd.arg(arg); + } + } + + // Doctests of `proc-macro` crates (and their dependencies) are always built for the host, + // so we are not able to run them in Miri. + if get_arg_flag_values("--crate-type").any(|crate_type| crate_type == "proc-macro") { + eprintln!("Running doctests of `proc-macro` crates is not currently supported by Miri."); + return; + } + + // For each doctest, rustdoc starts two child processes: first the test is compiled, + // then the produced executable is invoked. We want to reroute both of these to cargo-miri, + // such that the first time we'll enter phase_cargo_rustc, and phase_cargo_runner second. + // + // rustdoc invokes the test-builder by forwarding most of its own arguments, which makes + // it difficult to determine when phase_cargo_rustc should run instead of phase_cargo_rustdoc. + // Furthermore, the test code is passed via stdin, rather than a temporary file, so we need + // to let phase_cargo_rustc know to expect that. We'll use this environment variable as a flag: + cmd.env("MIRI_CALLED_FROM_RUSTDOC", "1"); + + // The `--test-builder` and `--runtool` arguments are unstable rustdoc features, + // which are disabled by default. We first need to enable them explicitly: + cmd.arg("-Z").arg("unstable-options"); + + // rustdoc needs to know the right sysroot. + cmd.arg("--sysroot").arg(env::var_os("MIRI_SYSROOT").unwrap()); + // make sure the 'miri' flag is set for rustdoc + cmd.arg("--cfg").arg("miri"); + + // Make rustdoc call us back. + let cargo_miri_path = std::env::current_exe().expect("current executable path invalid"); + cmd.arg("--test-builder").arg(&cargo_miri_path); // invoked by forwarding most arguments + cmd.arg("--runtool").arg(&cargo_miri_path); // invoked with just a single path argument + + debug_cmd("[cargo-miri rustdoc]", verbose, &cmd); + exec(cmd) +} diff --git a/src/tools/miri/cargo-miri/src/setup.rs b/src/tools/miri/cargo-miri/src/setup.rs new file mode 100644 index 0000000000000..c27bb18631758 --- /dev/null +++ b/src/tools/miri/cargo-miri/src/setup.rs @@ -0,0 +1,245 @@ +//! Implements `cargo miri setup` via xargo + +use std::env; +use std::ffi::OsStr; +use std::fs::{self}; +use std::io::BufRead; +use std::ops::Not; +use std::path::{Path, PathBuf}; +use std::process::{self, Command}; + +use crate::{util::*, version::*}; + +fn xargo_version() -> Option<(u32, u32, u32)> { + let out = xargo_check().arg("--version").output().ok()?; + if !out.status.success() { + return None; + } + // Parse output. The first line looks like "xargo 0.3.12 (b004f1c 2018-12-13)". + let line = out + .stderr + .lines() + .next() + .expect("malformed `xargo --version` output: not at least one line") + .expect("malformed `xargo --version` output: error reading first line"); + let (name, version) = { + let mut split = line.split(' '); + ( + split.next().expect("malformed `xargo --version` output: empty"), + split.next().expect("malformed `xargo --version` output: not at least two words"), + ) + }; + if name != "xargo" { + // This is some fork of xargo + return None; + } + let mut version_pieces = version.split('.'); + let major = version_pieces + .next() + .expect("malformed `xargo --version` output: not a major version piece") + .parse() + .expect("malformed `xargo --version` output: major version is not an integer"); + let minor = version_pieces + .next() + .expect("malformed `xargo --version` output: not a minor version piece") + .parse() + .expect("malformed `xargo --version` output: minor version is not an integer"); + let patch = version_pieces + .next() + .expect("malformed `xargo --version` output: not a patch version piece") + .parse() + .expect("malformed `xargo --version` output: patch version is not an integer"); + if version_pieces.next().is_some() { + panic!("malformed `xargo --version` output: more than three pieces in version"); + } + Some((major, minor, patch)) +} + +/// Performs the setup required to make `cargo miri` work: Getting a custom-built libstd. Then sets +/// `MIRI_SYSROOT`. Skipped if `MIRI_SYSROOT` is already set, in which case we expect the user has +/// done all this already. +pub fn setup(subcommand: &MiriCommand, host: &str, target: &str) { + let only_setup = matches!(subcommand, MiriCommand::Setup); + let ask_user = !only_setup; + let print_sysroot = only_setup && has_arg_flag("--print-sysroot"); // whether we just print the sysroot path + if std::env::var_os("MIRI_SYSROOT").is_some() { + if only_setup { + println!("WARNING: MIRI_SYSROOT already set, not doing anything.") + } + return; + } + + // First, we need xargo. + if xargo_version().map_or(true, |v| v < XARGO_MIN_VERSION) { + if std::env::var_os("XARGO_CHECK").is_some() { + // The user manually gave us a xargo binary; don't do anything automatically. + show_error!("xargo is too old; please upgrade to the latest version") + } + let mut cmd = cargo(); + cmd.args(["install", "xargo"]); + ask_to_run(cmd, ask_user, "install a recent enough xargo"); + } + + // Determine where the rust sources are located. The env vars manually setting the source + // (`MIRI_LIB_SRC`, `XARGO_RUST_SRC`) trump auto-detection. + let rust_src_env_var = + std::env::var_os("MIRI_LIB_SRC").or_else(|| std::env::var_os("XARGO_RUST_SRC")); + let rust_src = match rust_src_env_var { + Some(path) => { + let path = PathBuf::from(path); + // Make path absolute if possible. + path.canonicalize().unwrap_or(path) + } + None => { + // Check for `rust-src` rustup component. + let output = miri_for_host() + .args(["--print", "sysroot"]) + .output() + .expect("failed to determine sysroot"); + if !output.status.success() { + show_error!( + "Failed to determine sysroot; Miri said:\n{}", + String::from_utf8_lossy(&output.stderr).trim_end() + ); + } + let sysroot = std::str::from_utf8(&output.stdout).unwrap(); + let sysroot = Path::new(sysroot.trim_end_matches('\n')); + // Check for `$SYSROOT/lib/rustlib/src/rust/library`; test if that contains `std/Cargo.toml`. + let rustup_src = + sysroot.join("lib").join("rustlib").join("src").join("rust").join("library"); + if !rustup_src.join("std").join("Cargo.toml").exists() { + // Ask the user to install the `rust-src` component, and use that. + let mut cmd = Command::new("rustup"); + cmd.args(["component", "add", "rust-src"]); + ask_to_run( + cmd, + ask_user, + "install the `rust-src` component for the selected toolchain", + ); + } + rustup_src + } + }; + if !rust_src.exists() { + show_error!("given Rust source directory `{}` does not exist.", rust_src.display()); + } + if rust_src.file_name().and_then(OsStr::to_str) != Some("library") { + show_error!( + "given Rust source directory `{}` does not seem to be the `library` subdirectory of \ + a Rust source checkout.", + rust_src.display() + ); + } + + // Next, we need our own libstd. Prepare a xargo project for that purpose. + // We will do this work in whatever is a good cache dir for this platform. + let dirs = directories::ProjectDirs::from("org", "rust-lang", "miri").unwrap(); + let dir = dirs.cache_dir(); + if !dir.exists() { + fs::create_dir_all(dir).unwrap(); + } + // The interesting bit: Xargo.toml (only needs content if we actually need std) + let xargo_toml = if std::env::var_os("MIRI_NO_STD").is_some() { + "" + } else { + r#" +[dependencies.std] +default_features = false +# We support unwinding, so enable that panic runtime. +features = ["panic_unwind", "backtrace"] + +[dependencies.test] +"# + }; + write_to_file(&dir.join("Xargo.toml"), xargo_toml); + // The boring bits: a dummy project for xargo. + // FIXME: With xargo-check, can we avoid doing this? + write_to_file( + &dir.join("Cargo.toml"), + r#" +[package] +name = "miri-xargo" +description = "A dummy project for building libstd with xargo." +version = "0.0.0" + +[lib] +path = "lib.rs" +"#, + ); + write_to_file(&dir.join("lib.rs"), "#![no_std]"); + + // Figure out where xargo will build its stuff. + // Unfortunately, it puts things into a different directory when the + // architecture matches the host. + let sysroot = if target == host { dir.join("HOST") } else { PathBuf::from(dir) }; + // Make sure all target-level Miri invocations know their sysroot. + std::env::set_var("MIRI_SYSROOT", &sysroot); + + // Now invoke xargo. + let mut command = xargo_check(); + command.arg("check").arg("-q"); + command.current_dir(dir); + command.env("XARGO_HOME", dir); + command.env("XARGO_RUST_SRC", &rust_src); + // We always need to set a target so rustc bootstrap can tell apart host from target crates. + command.arg("--target").arg(target); + // Use Miri as rustc to build a libstd compatible with us (and use the right flags). + // However, when we are running in bootstrap, we cannot just overwrite `RUSTC`, + // because we still need bootstrap to distinguish between host and target crates. + // In that case we overwrite `RUSTC_REAL` instead which determines the rustc used + // for target crates. + // We set ourselves (`cargo-miri`) instead of Miri directly to be able to patch the flags + // for `libpanic_abort` (usually this is done by bootstrap but we have to do it ourselves). + // The `MIRI_CALLED_FROM_XARGO` will mean we dispatch to `phase_setup_rustc`. + let cargo_miri_path = std::env::current_exe().expect("current executable path invalid"); + if env::var_os("RUSTC_STAGE").is_some() { + assert!(env::var_os("RUSTC").is_some()); + command.env("RUSTC_REAL", &cargo_miri_path); + } else { + command.env("RUSTC", &cargo_miri_path); + } + command.env("MIRI_CALLED_FROM_XARGO", "1"); + // Make sure there are no other wrappers getting in our way + // (Cc https://github.com/rust-lang/miri/issues/1421, https://github.com/rust-lang/miri/issues/2429). + // Looks like setting `RUSTC_WRAPPER` to the empty string overwrites `build.rustc-wrapper` set via `config.toml`. + command.env("RUSTC_WRAPPER", ""); + // Disable debug assertions in the standard library -- Miri is already slow enough. But keep the + // overflow checks, they are cheap. This completely overwrites flags the user might have set, + // which is consistent with normal `cargo build` that does not apply `RUSTFLAGS` to the sysroot + // either. + command.env("RUSTFLAGS", "-Cdebug-assertions=off -Coverflow-checks=on"); + // Manage the output the user sees. + if only_setup { + // We want to be explicit. + eprintln!("Preparing a sysroot for Miri (target: {target})..."); + if print_sysroot { + // Be extra sure there is no noise on stdout. + command.stdout(process::Stdio::null()); + } + } else { + // We want to be quiet, but still let the user know that something is happening. + eprint!("Preparing a sysroot for Miri (target: {target})... "); + command.stdout(process::Stdio::null()); + command.stderr(process::Stdio::null()); + } + + // Finally run it! + if command.status().expect("failed to run xargo").success().not() { + if only_setup { + show_error!("failed to run xargo, see error details above") + } else { + show_error!("failed to run xargo; run `cargo miri setup` to see the error details") + } + } + + // Figure out what to print. + if only_setup { + eprintln!("A sysroot for Miri is now available in `{}`.", sysroot.display()); + } else { + eprintln!("done"); + } + if print_sysroot { + // Print just the sysroot and nothing else to stdout; this way we do not need any escaping. + println!("{}", sysroot.display()); + } +} diff --git a/src/tools/miri/cargo-miri/src/util.rs b/src/tools/miri/cargo-miri/src/util.rs new file mode 100644 index 0000000000000..8f29eebaac100 --- /dev/null +++ b/src/tools/miri/cargo-miri/src/util.rs @@ -0,0 +1,314 @@ +use std::collections::HashMap; +use std::env; +use std::ffi::OsString; +use std::fmt::Write as _; +use std::fs::{self, File}; +use std::io::{self, BufWriter, Read, Write}; +use std::ops::Not; +use std::path::{Path, PathBuf}; +use std::process::Command; + +use cargo_metadata::{Metadata, MetadataCommand}; +use rustc_version::VersionMeta; +use serde::{Deserialize, Serialize}; + +pub use crate::arg::*; + +pub fn show_error(msg: &impl std::fmt::Display) -> ! { + eprintln!("fatal error: {msg}"); + std::process::exit(1) +} + +macro_rules! show_error { + ($($tt:tt)*) => { crate::util::show_error(&format_args!($($tt)*)) }; +} + +/// The information to run a crate with the given environment. +#[derive(Clone, Serialize, Deserialize)] +pub struct CrateRunEnv { + /// The command-line arguments. + pub args: Vec, + /// The environment. + pub env: Vec<(OsString, OsString)>, + /// The current working directory. + pub current_dir: OsString, + /// The contents passed via standard input. + pub stdin: Vec, +} + +impl CrateRunEnv { + /// Gather all the information we need. + pub fn collect(args: impl Iterator, capture_stdin: bool) -> Self { + let args = args.collect(); + let env = env::vars_os().collect(); + let current_dir = env::current_dir().unwrap().into_os_string(); + + let mut stdin = Vec::new(); + if capture_stdin { + std::io::stdin().lock().read_to_end(&mut stdin).expect("cannot read stdin"); + } + + CrateRunEnv { args, env, current_dir, stdin } + } +} + +/// The information Miri needs to run a crate. Stored as JSON when the crate is "compiled". +#[derive(Serialize, Deserialize)] +pub enum CrateRunInfo { + /// Run it with the given environment. + RunWith(CrateRunEnv), + /// Skip it as Miri does not support interpreting such kind of crates. + SkipProcMacroTest, +} + +impl CrateRunInfo { + pub fn store(&self, filename: &Path) { + let file = File::create(filename) + .unwrap_or_else(|_| show_error!("cannot create `{}`", filename.display())); + let file = BufWriter::new(file); + serde_json::ser::to_writer(file, self) + .unwrap_or_else(|_| show_error!("cannot write to `{}`", filename.display())); + } +} + +#[derive(Clone, Debug)] +pub enum MiriCommand { + /// Our own special 'setup' command. + Setup, + /// A command to be forwarded to cargo. + Forward(String), +} + +/// Escapes `s` in a way that is suitable for using it as a string literal in TOML syntax. +pub fn escape_for_toml(s: &str) -> String { + // We want to surround this string in quotes `"`. So we first escape all quotes, + // and also all backslashes (that are used to escape quotes). + let s = s.replace('\\', r#"\\"#).replace('"', r#"\""#); + format!("\"{}\"", s) +} + +/// Returns the path to the `miri` binary +pub fn find_miri() -> PathBuf { + if let Some(path) = env::var_os("MIRI") { + return path.into(); + } + let mut path = std::env::current_exe().expect("current executable path invalid"); + if cfg!(windows) { + path.set_file_name("miri.exe"); + } else { + path.set_file_name("miri"); + } + path +} + +pub fn miri() -> Command { + Command::new(find_miri()) +} + +pub fn miri_for_host() -> Command { + let mut cmd = miri(); + cmd.env("MIRI_BE_RUSTC", "host"); + cmd +} + +pub fn version_info() -> VersionMeta { + VersionMeta::for_command(miri_for_host()) + .expect("failed to determine underlying rustc version of Miri") +} + +pub fn cargo() -> Command { + Command::new(env::var_os("CARGO").unwrap_or_else(|| OsString::from("cargo"))) +} + +pub fn xargo_check() -> Command { + Command::new(env::var_os("XARGO_CHECK").unwrap_or_else(|| OsString::from("xargo-check"))) +} + +/// Execute the `Command`, where possible by replacing the current process with a new process +/// described by the `Command`. Then exit this process with the exit code of the new process. +pub fn exec(mut cmd: Command) -> ! { + // On non-Unix imitate POSIX exec as closely as we can + #[cfg(not(unix))] + { + let exit_status = cmd.status().expect("failed to run command"); + std::process::exit(exit_status.code().unwrap_or(-1)) + } + // On Unix targets, actually exec. + // If exec returns, process setup has failed. This is the same error condition as the expect in + // the non-Unix case. + #[cfg(unix)] + { + use std::os::unix::process::CommandExt; + let error = cmd.exec(); + Err(error).expect("failed to run command") + } +} + +/// Execute the `Command`, where possible by replacing the current process with a new process +/// described by the `Command`. Then exit this process with the exit code of the new process. +/// `input` is also piped to the new process's stdin, on cfg(unix) platforms by writing its +/// contents to `path` first, then setting stdin to that file. +pub fn exec_with_pipe

(mut cmd: Command, input: &[u8], path: P) -> ! +where + P: AsRef, +{ + #[cfg(unix)] + { + // Write the bytes we want to send to stdin out to a file + std::fs::write(&path, input).unwrap(); + // Open the file for reading, and set our new stdin to it + let stdin = File::open(&path).unwrap(); + cmd.stdin(stdin); + // Unlink the file so that it is fully cleaned up as soon as the new process exits + std::fs::remove_file(&path).unwrap(); + // Finally, we can hand off control. + exec(cmd) + } + #[cfg(not(unix))] + { + drop(path); // We don't need the path, we can pipe the bytes directly + cmd.stdin(std::process::Stdio::piped()); + let mut child = cmd.spawn().expect("failed to spawn process"); + { + let stdin = child.stdin.as_mut().expect("failed to open stdin"); + stdin.write_all(input).expect("failed to write out test source"); + } + let exit_status = child.wait().expect("failed to run command"); + std::process::exit(exit_status.code().unwrap_or(-1)) + } +} + +pub fn ask_to_run(mut cmd: Command, ask: bool, text: &str) { + // Disable interactive prompts in CI (GitHub Actions, Travis, AppVeyor, etc). + // Azure doesn't set `CI` though (nothing to see here, just Microsoft being Microsoft), + // so we also check their `TF_BUILD`. + let is_ci = env::var_os("CI").is_some() || env::var_os("TF_BUILD").is_some(); + if ask && !is_ci { + let mut buf = String::new(); + print!("I will run `{:?}` to {}. Proceed? [Y/n] ", cmd, text); + io::stdout().flush().unwrap(); + io::stdin().read_line(&mut buf).unwrap(); + match buf.trim().to_lowercase().as_ref() { + // Proceed. + "" | "y" | "yes" => {} + "n" | "no" => show_error!("aborting as per your request"), + a => show_error!("invalid answer `{}`", a), + }; + } else { + eprintln!("Running `{:?}` to {}.", cmd, text); + } + + if cmd.status().unwrap_or_else(|_| panic!("failed to execute {:?}", cmd)).success().not() { + show_error!("failed to {}", text); + } +} + +/// Writes the given content to the given file *cross-process atomically*, in the sense that another +/// process concurrently reading that file will see either the old content or the new content, but +/// not some intermediate (e.g., empty) state. +/// +/// We assume no other parts of this same process are trying to read or write that file. +pub fn write_to_file(filename: &Path, content: &str) { + // Create a temporary file with the desired contents. + let mut temp_filename = filename.as_os_str().to_os_string(); + temp_filename.push(&format!(".{}", std::process::id())); + let mut temp_file = File::create(&temp_filename).unwrap(); + temp_file.write_all(content.as_bytes()).unwrap(); + drop(temp_file); + + // Move file to the desired location. + fs::rename(temp_filename, filename).unwrap(); +} + +// Computes the extra flags that need to be passed to cargo to make it behave like the current +// cargo invocation. +fn cargo_extra_flags() -> Vec { + let mut flags = Vec::new(); + // `-Zunstable-options` is required by `--config`. + flags.push("-Zunstable-options".to_string()); + + // Forward `--config` flags. + let config_flag = "--config"; + for arg in get_arg_flag_values(config_flag) { + flags.push(config_flag.to_string()); + flags.push(arg); + } + + // Forward `--manifest-path`. + let manifest_flag = "--manifest-path"; + if let Some(manifest) = get_arg_flag_value(manifest_flag) { + flags.push(manifest_flag.to_string()); + flags.push(manifest); + } + + // Forwarding `--target-dir` would make sense, but `cargo metadata` does not support that flag. + + flags +} + +pub fn get_cargo_metadata() -> Metadata { + // This will honor the `CARGO` env var the same way our `cargo()` does. + MetadataCommand::new().no_deps().other_options(cargo_extra_flags()).exec().unwrap() +} + +/// Pulls all the crates in this workspace from the cargo metadata. +/// Workspace members are emitted like "miri 0.1.0 (path+file:///path/to/miri)" +/// Additionally, somewhere between cargo metadata and TyCtxt, '-' gets replaced with '_' so we +/// make that same transformation here. +pub fn local_crates(metadata: &Metadata) -> String { + assert!(!metadata.workspace_members.is_empty()); + let mut local_crates = String::new(); + for member in &metadata.workspace_members { + let name = member.repr.split(' ').next().unwrap(); + let name = name.replace('-', "_"); + local_crates.push_str(&name); + local_crates.push(','); + } + local_crates.pop(); // Remove the trailing ',' + + local_crates +} + +fn env_vars_from_cmd(cmd: &Command) -> Vec<(String, String)> { + let mut envs = HashMap::new(); + for (key, value) in std::env::vars() { + envs.insert(key, value); + } + for (key, value) in cmd.get_envs() { + if let Some(value) = value { + envs.insert(key.to_string_lossy().to_string(), value.to_string_lossy().to_string()); + } else { + envs.remove(&key.to_string_lossy().to_string()); + } + } + let mut envs: Vec<_> = envs.into_iter().collect(); + envs.sort(); + envs +} + +/// Debug-print a command that is going to be run. +pub fn debug_cmd(prefix: &str, verbose: usize, cmd: &Command) { + if verbose == 0 { + return; + } + // We only do a single `eprintln!` call to minimize concurrency interactions. + let mut out = prefix.to_string(); + writeln!(out, " running command: env \\").unwrap(); + if verbose > 1 { + // Print the full environment this will be called in. + for (key, value) in env_vars_from_cmd(cmd) { + writeln!(out, "{key}={value:?} \\").unwrap(); + } + } else { + // Print only what has been changed for this `cmd`. + for (var, val) in cmd.get_envs() { + if let Some(val) = val { + writeln!(out, "{}={:?} \\", var.to_string_lossy(), val).unwrap(); + } else { + writeln!(out, "--unset={}", var.to_string_lossy()).unwrap(); + } + } + } + write!(out, "{cmd:?}").unwrap(); + eprintln!("{}", out); +} diff --git a/src/tools/miri/cargo-miri/src/version.rs b/src/tools/miri/cargo-miri/src/version.rs new file mode 100644 index 0000000000000..366e90df17983 --- /dev/null +++ b/src/tools/miri/cargo-miri/src/version.rs @@ -0,0 +1,2 @@ +// We put this in a separate file so that it can be hashed for GHA caching. +pub const XARGO_MIN_VERSION: (u32, u32, u32) = (0, 3, 26); diff --git a/src/tools/miri/ci.sh b/src/tools/miri/ci.sh new file mode 100755 index 0000000000000..aa322e54a31da --- /dev/null +++ b/src/tools/miri/ci.sh @@ -0,0 +1,101 @@ +#!/bin/bash +set -euo pipefail +set -x + +# Determine configuration for installed build +echo "Installing release version of Miri" +export RUSTFLAGS="-D warnings" +export CARGO_INCREMENTAL=0 +./miri install # implicitly locked + +# Prepare debug build for direct `./miri` invocations +echo "Building debug version of Miri" +export CARGO_EXTRA_FLAGS="--locked" +./miri check --no-default-features # make sure this can be built +./miri check --all-features # and this, too +./miri build --all-targets # the build that all the `./miri test` below will use +echo + +# Test +function run_tests { + if [ -n "${MIRI_TEST_TARGET+exists}" ]; then + echo "Testing foreign architecture $MIRI_TEST_TARGET" + else + echo "Testing host architecture" + fi + + ## ui test suite + ./miri test + if [ -z "${MIRI_TEST_TARGET+exists}" ]; then + # Only for host architecture: tests with optimizations (`-O` is what cargo passes, but crank MIR + # optimizations up all the way). + # Optimizations change diagnostics (mostly backtraces), so we don't check them + #FIXME(#2155): we want to only run the pass and panic tests here, not the fail tests. + MIRIFLAGS="${MIRIFLAGS:-} -O -Zmir-opt-level=4" MIRI_SKIP_UI_CHECKS=1 ./miri test -- tests/{pass,panic} + fi + + ## test-cargo-miri + # On Windows, there is always "python", not "python3" or "python2". + if command -v python3 > /dev/null; then + PYTHON=python3 + else + PYTHON=python + fi + # Some environment setup that attempts to confuse the heck out of cargo-miri. + if [ "$HOST_TARGET" = x86_64-unknown-linux-gnu ]; then + # These act up on Windows (`which miri` produces a filename that does not exist?!?), + # so let's do this only on Linux. Also makes sure things work without these set. + export RUSTC=$(which rustc) + export MIRI=$(which miri) + fi + mkdir -p .cargo + echo 'build.rustc-wrapper = "thisdoesnotexist"' > .cargo/config.toml + # Run the actual test + ${PYTHON} test-cargo-miri/run-test.py + echo + # Clean up + unset RUSTC MIRI + rm -rf .cargo + + # Ensure that our benchmarks all work, but only on Linux hosts. + if [ -z "${MIRI_TEST_TARGET+exists}" ] && [ "$HOST_TARGET" = x86_64-unknown-linux-gnu ] ; then + for BENCH in $(ls "bench-cargo-miri"); do + cargo miri run --manifest-path bench-cargo-miri/$BENCH/Cargo.toml + done + fi +} + +function run_tests_minimal { + if [ -n "${MIRI_TEST_TARGET+exists}" ]; then + echo "Testing MINIMAL foreign architecture $MIRI_TEST_TARGET: only testing $@" + else + echo "Testing MINIMAL host architecture: only testing $@" + fi + + ./miri test -- "$@" +} + +# host +run_tests + +case $HOST_TARGET in + x86_64-unknown-linux-gnu) + MIRI_TEST_TARGET=i686-unknown-linux-gnu run_tests + MIRI_TEST_TARGET=aarch64-apple-darwin run_tests + MIRI_TEST_TARGET=i686-pc-windows-msvc run_tests + MIRI_TEST_TARGET=x86_64-unknown-freebsd run_tests_minimal hello integer vec panic/panic concurrency/simple atomic data_race env/var + MIRI_TEST_TARGET=aarch64-linux-android run_tests_minimal hello integer vec panic/panic + MIRI_TEST_TARGET=thumbv7em-none-eabihf MIRI_NO_STD=1 run_tests_minimal no_std # no_std embedded architecture + ;; + x86_64-apple-darwin) + MIRI_TEST_TARGET=mips64-unknown-linux-gnuabi64 run_tests # big-endian architecture + MIRI_TEST_TARGET=x86_64-pc-windows-msvc run_tests + ;; + i686-pc-windows-msvc) + MIRI_TEST_TARGET=x86_64-unknown-linux-gnu run_tests + ;; + *) + echo "FATAL: unknown OS" + exit 1 + ;; +esac diff --git a/src/tools/miri/miri b/src/tools/miri/miri new file mode 100755 index 0000000000000..19f1a987ace4a --- /dev/null +++ b/src/tools/miri/miri @@ -0,0 +1,236 @@ +#!/bin/bash +set -e +USAGE=$(cat <<"EOF" + COMMANDS + +./miri install : +Installs the miri driver and cargo-miri. are passed to `cargo +install`. Sets up the rpath such that the installed binary should work in any +working directory. However, the rustup toolchain when invoking `cargo miri` +needs to be the same one used for `./miri install`. + +./miri build : +Just build miri. are passed to `cargo build`. + +./miri check : +Just check miri. are passed to `cargo check`. + +./miri test : +Build miri, set up a sysroot and then run the test suite. are passed +to the final `cargo test` invocation. + +./miri run : +Build miri, set up a sysroot and then run the driver with the given . +(Also respects MIRIFLAGS environment variable.) + +./miri fmt : +Format all sources and tests. are passed to `rustfmt`. + +./miri clippy : +Runs clippy on all sources. are passed to `cargo clippy`. + +./miri cargo : +Runs just `cargo ` with the Miri-specific environment variables. +Mainly meant to be invoked by rust-analyzer. + +./miri many-seeds : +Runs over and over again with different seeds for Miri. The MIRIFLAGS +variable is set to its original value appended with ` -Zmiri-seed=$SEED` for +many different seeds. + +./miri bench : +Runs the benchmarks from bench-cargo-miri in hyperfine. hyperfine needs to be installed. + can explicitly list the benchmarks to run; by default, all of them are run. + + ENVIRONMENT VARIABLES + +MIRI_SYSROOT: +If already set, the "sysroot setup" step is skipped. + +CARGO_EXTRA_FLAGS: +Pass extra flags to all cargo invocations. (Ignored by `./miri cargo`.) +EOF +) + +## We need to know where we are. +# macOS does not have a useful readlink/realpath so we have to use Python instead... +MIRIDIR=$(python3 -c 'import os, sys; print(os.path.dirname(os.path.realpath(sys.argv[1])))' "$0") + +## Run the auto-things. +if [ -z "$MIRI_AUTO_OPS" ]; then + export MIRI_AUTO_OPS=42 + + # Run this first, so that the toolchain doesn't change after + # other code has run. + if [ -f "$MIRIDIR/.auto-everything" ] || [ -f "$MIRIDIR/.auto-toolchain" ] ; then + (cd "$MIRIDIR" && ./rustup-toolchain) + fi + + if [ -f "$MIRIDIR/.auto-everything" ] || [ -f "$MIRIDIR/.auto-fmt" ] ; then + $0 fmt + fi + + if [ -f "$MIRIDIR/.auto-everything" ] || [ -f "$MIRIDIR/.auto-clippy" ] ; then + $0 clippy -- -D warnings + fi +fi + +## Determine command and toolchain. +COMMAND="$1" +[ $# -gt 0 ] && shift +# Doing this *after* auto-toolchain logic above, since that might change the toolchain. +TOOLCHAIN=$(cd "$MIRIDIR"; rustup show active-toolchain | head -n 1 | cut -d ' ' -f 1) + +## Handle some commands early, since they should *not* alter the environment. +case "$COMMAND" in +many-seeds) + for SEED in $({ echo obase=16; seq 0 255; } | bc); do + echo "Trying seed: $SEED" + MIRIFLAGS="$MIRIFLAGS -Zmiri-seed=$SEED" $@ || { echo "Failing seed: $SEED"; break; } + done + exit 0 + ;; +bench) + # Make sure we have an up-to-date Miri installed + "$0" install + # Run the requested benchmarks + if [ -z "${1+exists}" ]; then + BENCHES=( $(ls "$MIRIDIR/bench-cargo-miri" ) ) + else + BENCHES=("$@") + fi + for BENCH in "${BENCHES[@]}"; do + hyperfine -w 1 -m 5 --shell=none "cargo +$TOOLCHAIN miri run --manifest-path $MIRIDIR/bench-cargo-miri/$BENCH/Cargo.toml" + done + exit 0 + ;; +esac + +## Prepare the environment +# Determine some toolchain properties +# export the target so its available in miri +TARGET=$(rustc +$TOOLCHAIN --version --verbose | grep "^host:" | cut -d ' ' -f 2) +SYSROOT=$(rustc +$TOOLCHAIN --print sysroot) +LIBDIR=$SYSROOT/lib/rustlib/$TARGET/lib +if ! test -d "$LIBDIR"; then + echo "Something went wrong determining the library dir." + echo "I got $LIBDIR but that does not exist." + echo "Please report a bug at https://github.com/rust-lang/miri/issues." + exit 2 +fi + +# Prepare flags for cargo and rustc. +CARGO="cargo +$TOOLCHAIN" +# Share target dir between `miri` and `cargo-miri`. +if [ -z "$CARGO_TARGET_DIR" ]; then + export CARGO_TARGET_DIR="$MIRIDIR/target" +fi +# We configure dev builds to not be unusably slow. +if [ -z "$CARGO_PROFILE_DEV_OPT_LEVEL" ]; then + export CARGO_PROFILE_DEV_OPT_LEVEL=2 +fi +# Enable rustc-specific lints (ignored without `-Zunstable-options`). +export RUSTFLAGS="-Zunstable-options -Wrustc::internal $RUSTFLAGS" +# We set the rpath so that Miri finds the private rustc libraries it needs. +export RUSTFLAGS="-C link-args=-Wl,-rpath,$LIBDIR $RUSTFLAGS" + +## Helper functions + +# Build a sysroot and set MIRI_SYSROOT to use it. Arguments are passed to `cargo miri setup`. +build_sysroot() { + if ! MIRI_SYSROOT="$($CARGO run $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/cargo-miri/Cargo.toml -q -- miri setup --print-sysroot "$@")"; then + echo "'cargo miri setup' failed" + exit 1 + fi + export MIRI_SYSROOT +} + +# Prepare and set MIRI_SYSROOT. Respects `MIRI_TEST_TARGET` and takes into account +# locally built vs. distributed rustc. +find_sysroot() { + if [ -n "$MIRI_SYSROOT" ]; then + # Sysroot already set, use that. + return 0 + fi + # We need to build a sysroot. + if [ -n "$MIRI_TEST_TARGET" ]; then + build_sysroot --target "$MIRI_TEST_TARGET" + else + build_sysroot + fi +} + +## Main + +# Run command. +case "$COMMAND" in +install) + # "--locked" to respect the Cargo.lock file if it exists. + $CARGO install $CARGO_EXTRA_FLAGS --path "$MIRIDIR" --force --locked "$@" + $CARGO install $CARGO_EXTRA_FLAGS --path "$MIRIDIR"/cargo-miri --force --locked "$@" + ;; +check) + # Check, and let caller control flags. + $CARGO check $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml --all-targets "$@" + $CARGO check $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/cargo-miri/Cargo.toml "$@" + ;; +build) + # Build, and let caller control flags. + $CARGO build $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml "$@" + $CARGO build $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/cargo-miri/Cargo.toml "$@" + ;; +test|bless) + # First build and get a sysroot. + $CARGO build $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml + find_sysroot + if [ "$COMMAND" = "bless" ]; then + export MIRI_BLESS="Gesundheit" + fi + # Then test, and let caller control flags. + # Only in root project as `cargo-miri` has no tests. + $CARGO test $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml "$@" + ;; +run) + # Scan for "--target" to overwrite the "MIRI_TEST_TARGET" env var so + # that we set the MIRI_SYSROOT up the right way. + FOUND_TARGET_OPT=0 + for ARG in "$@"; do + if [ "$LAST_ARG" = "--target" ]; then + # Found it! + export MIRI_TEST_TARGET="$ARG" + FOUND_TARGET_OPT=1 + break + fi + LAST_ARG="$ARG" + done + if [ "$FOUND_TARGET_OPT" = "0" ] && [ -n "$MIRI_TEST_TARGET" ]; then + # Make sure Miri actually uses this target. + MIRIFLAGS="$MIRIFLAGS --target $MIRI_TEST_TARGET" + fi + # First build and get a sysroot. + $CARGO build $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml + find_sysroot + # Then run the actual command. + exec $CARGO run $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml -- $MIRIFLAGS "$@" + ;; +fmt) + find "$MIRIDIR" -not \( -name target -prune \) -name '*.rs' \ + | xargs rustfmt +$TOOLCHAIN --edition=2021 --config-path "$MIRIDIR/rustfmt.toml" "$@" + ;; +clippy) + $CARGO clippy $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml --all-targets "$@" + $CARGO clippy $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/cargo-miri/Cargo.toml "$@" + ;; +cargo) + # We carefully kept the working dir intact, so this will run cargo *on the workspace in the + # current working dir*, not on the main Miri workspace. That is exactly what RA needs. + $CARGO "$@" + ;; +*) + if [ -n "$COMMAND" ]; then + echo "Unknown command: $COMMAND" + echo + fi + echo "$USAGE" + exit 1 +esac diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version new file mode 100644 index 0000000000000..78b9a110aa7c7 --- /dev/null +++ b/src/tools/miri/rust-version @@ -0,0 +1 @@ +acb8934fd57b3c2740c4abac0a5728c2c9b1423b diff --git a/src/tools/miri/rustfmt.toml b/src/tools/miri/rustfmt.toml new file mode 100644 index 0000000000000..be5af7379eae7 --- /dev/null +++ b/src/tools/miri/rustfmt.toml @@ -0,0 +1,5 @@ +version = "Two" +use_small_heuristics = "Max" +match_arm_blocks = false +match_arm_leading_pipes = "Preserve" +force_multiline_blocks = true diff --git a/src/tools/miri/rustup-toolchain b/src/tools/miri/rustup-toolchain new file mode 100755 index 0000000000000..d7730f2b06d36 --- /dev/null +++ b/src/tools/miri/rustup-toolchain @@ -0,0 +1,53 @@ +#!/bin/bash +set -e +# Manages a rustup toolchain called "miri". +# +# All commands set "miri" as the override toolchain for the current directory, +# and make the `rust-version` file match that toolchain. +# +# USAGE: +# +# ./rustup-toolchain: Update "miri" toolchain to match `rust-version` (the known-good version for this commit). +# +# ./rustup-toolchain HEAD: Update "miri" toolchain and `rust-version` file to latest rustc HEAD. +# +# ./rustup-toolchain $COMMIT: Update "miri" toolchain and `rust-version` file to match that commit. +# +# Any extra parameters are passed to `rustup-toolchain-install-master`. + +# Make sure rustup-toolchain-install-master is installed. +if ! which rustup-toolchain-install-master >/dev/null; then + echo "Please install rustup-toolchain-install-master by running 'cargo install rustup-toolchain-install-master'" + exit 1 +fi + +# Determine new commit. +if [[ "$1" == "" ]]; then + NEW_COMMIT=$(cat rust-version) +elif [[ "$1" == "HEAD" ]]; then + NEW_COMMIT=$(git ls-remote https://github.com/rust-lang/rust/ HEAD | cut -f 1) +else + NEW_COMMIT="$1" +fi +echo "$NEW_COMMIT" > rust-version +shift || true # don't fail if shifting fails + +# Check if we already are at that commit. +CUR_COMMIT=$(rustc +miri --version -v 2>/dev/null | grep "^commit-hash: " | cut -d " " -f 2) +if [[ "$CUR_COMMIT" == "$NEW_COMMIT" ]]; then + echo "miri toolchain is already at commit $CUR_COMMIT." + rustup override set miri + exit 0 +fi + +# Install and setup new toolchain. +rustup toolchain uninstall miri +rustup-toolchain-install-master -n miri -c cargo -c rust-src -c rustc-dev -c llvm-tools -c rustfmt -c clippy "$@" -- "$NEW_COMMIT" +rustup override set miri + +# Cleanup. +cargo clean + +# Call 'cargo metadata' on the sources in case that changes the lockfile +# (which fails under some setups when it is done from inside vscode). +cargo metadata --format-version 1 --manifest-path "$(rustc --print sysroot)/lib/rustlib/rustc-src/rust/compiler/rustc/Cargo.toml" >/dev/null diff --git a/src/tools/miri/src/bin/miri.rs b/src/tools/miri/src/bin/miri.rs new file mode 100644 index 0000000000000..7d32ee4257326 --- /dev/null +++ b/src/tools/miri/src/bin/miri.rs @@ -0,0 +1,562 @@ +#![feature(rustc_private, stmt_expr_attributes)] +#![allow( + clippy::manual_range_contains, + clippy::useless_format, + clippy::field_reassign_with_default +)] + +extern crate rustc_data_structures; +extern crate rustc_driver; +extern crate rustc_hir; +extern crate rustc_interface; +extern crate rustc_metadata; +extern crate rustc_middle; +extern crate rustc_session; + +use std::env; +use std::num::NonZeroU64; +use std::path::PathBuf; +use std::str::FromStr; + +use log::debug; + +use rustc_data_structures::sync::Lrc; +use rustc_driver::Compilation; +use rustc_hir::{self as hir, def_id::LOCAL_CRATE, Node}; +use rustc_interface::interface::Config; +use rustc_middle::{ + middle::exported_symbols::{ + ExportedSymbol, SymbolExportInfo, SymbolExportKind, SymbolExportLevel, + }, + ty::{query::ExternProviders, TyCtxt}, +}; +use rustc_session::{config::CrateType, search_paths::PathKind, CtfeBacktrace}; + +use miri::{BacktraceStyle, ProvenanceMode}; + +struct MiriCompilerCalls { + miri_config: miri::MiriConfig, +} + +impl rustc_driver::Callbacks for MiriCompilerCalls { + fn config(&mut self, config: &mut Config) { + config.override_queries = Some(|_, _, external_providers| { + external_providers.used_crate_source = |tcx, cnum| { + let mut providers = ExternProviders::default(); + rustc_metadata::provide_extern(&mut providers); + let mut crate_source = (providers.used_crate_source)(tcx, cnum); + // HACK: rustc will emit "crate ... required to be available in rlib format, but + // was not found in this form" errors once we use `tcx.dependency_formats()` if + // there's no rlib provided, so setting a dummy path here to workaround those errors. + Lrc::make_mut(&mut crate_source).rlib = Some((PathBuf::new(), PathKind::All)); + crate_source + }; + }); + } + + fn after_analysis<'tcx>( + &mut self, + compiler: &rustc_interface::interface::Compiler, + queries: &'tcx rustc_interface::Queries<'tcx>, + ) -> Compilation { + compiler.session().abort_if_errors(); + + queries.global_ctxt().unwrap().peek_mut().enter(|tcx| { + init_late_loggers(tcx); + if !tcx.sess.crate_types().contains(&CrateType::Executable) { + tcx.sess.fatal("miri only makes sense on bin crates"); + } + + let (entry_def_id, entry_type) = if let Some(entry_def) = tcx.entry_fn(()) { + entry_def + } else { + tcx.sess.fatal("miri can only run programs that have a main function"); + }; + let mut config = self.miri_config.clone(); + + // Add filename to `miri` arguments. + config.args.insert(0, compiler.input().filestem().to_string()); + + // Adjust working directory for interpretation. + if let Some(cwd) = env::var_os("MIRI_CWD") { + env::set_current_dir(cwd).unwrap(); + } + + if let Some(return_code) = miri::eval_entry(tcx, entry_def_id, entry_type, config) { + std::process::exit( + i32::try_from(return_code).expect("Return value was too large!"), + ); + } + }); + + compiler.session().abort_if_errors(); + + Compilation::Stop + } +} + +struct MiriBeRustCompilerCalls { + target_crate: bool, +} + +impl rustc_driver::Callbacks for MiriBeRustCompilerCalls { + #[allow(rustc::potential_query_instability)] // rustc_codegen_ssa (where this code is copied from) also allows this lint + fn config(&mut self, config: &mut Config) { + if config.opts.prints.is_empty() && self.target_crate { + // Queries overriden here affect the data stored in `rmeta` files of dependencies, + // which will be used later in non-`MIRI_BE_RUSTC` mode. + config.override_queries = Some(|_, local_providers, _| { + // `exported_symbols` and `reachable_non_generics` provided by rustc always returns + // an empty result if `tcx.sess.opts.output_types.should_codegen()` is false. + local_providers.exported_symbols = |tcx, cnum| { + assert_eq!(cnum, LOCAL_CRATE); + tcx.arena.alloc_from_iter( + // This is based on: + // https://github.com/rust-lang/rust/blob/2962e7c0089d5c136f4e9600b7abccfbbde4973d/compiler/rustc_codegen_ssa/src/back/symbol_export.rs#L62-L63 + // https://github.com/rust-lang/rust/blob/2962e7c0089d5c136f4e9600b7abccfbbde4973d/compiler/rustc_codegen_ssa/src/back/symbol_export.rs#L174 + tcx.reachable_set(()).iter().filter_map(|&local_def_id| { + // Do the same filtering that rustc does: + // https://github.com/rust-lang/rust/blob/2962e7c0089d5c136f4e9600b7abccfbbde4973d/compiler/rustc_codegen_ssa/src/back/symbol_export.rs#L84-L102 + // Otherwise it may cause unexpected behaviours and ICEs + // (https://github.com/rust-lang/rust/issues/86261). + let is_reachable_non_generic = matches!( + tcx.hir().get(tcx.hir().local_def_id_to_hir_id(local_def_id)), + Node::Item(&hir::Item { + kind: hir::ItemKind::Static(..) | hir::ItemKind::Fn(..), + .. + }) | Node::ImplItem(&hir::ImplItem { + kind: hir::ImplItemKind::Fn(..), + .. + }) + if !tcx.generics_of(local_def_id).requires_monomorphization(tcx) + ); + (is_reachable_non_generic + && tcx.codegen_fn_attrs(local_def_id).contains_extern_indicator()) + .then_some(( + ExportedSymbol::NonGeneric(local_def_id.to_def_id()), + // Some dummy `SymbolExportInfo` here. We only use + // `exported_symbols` in shims/foreign_items.rs and the export info + // is ignored. + SymbolExportInfo { + level: SymbolExportLevel::C, + kind: SymbolExportKind::Text, + used: false, + }, + )) + }), + ) + } + }); + } + } +} + +fn show_error(msg: &impl std::fmt::Display) -> ! { + eprintln!("fatal error: {msg}"); + std::process::exit(1) +} + +macro_rules! show_error { + ($($tt:tt)*) => { show_error(&format_args!($($tt)*)) }; +} + +fn init_early_loggers() { + // Note that our `extern crate log` is *not* the same as rustc's; as a result, we have to + // initialize them both, and we always initialize `miri`'s first. + let env = env_logger::Env::new().filter("MIRI_LOG").write_style("MIRI_LOG_STYLE"); + env_logger::init_from_env(env); + // Enable verbose entry/exit logging by default if MIRI_LOG is set. + if env::var_os("MIRI_LOG").is_some() && env::var_os("RUSTC_LOG_ENTRY_EXIT").is_none() { + env::set_var("RUSTC_LOG_ENTRY_EXIT", "1"); + } + // We only initialize `rustc` if the env var is set (so the user asked for it). + // If it is not set, we avoid initializing now so that we can initialize + // later with our custom settings, and *not* log anything for what happens before + // `miri` gets started. + if env::var_os("RUSTC_LOG").is_some() { + rustc_driver::init_rustc_env_logger(); + } +} + +fn init_late_loggers(tcx: TyCtxt<'_>) { + // We initialize loggers right before we start evaluation. We overwrite the `RUSTC_LOG` + // env var if it is not set, control it based on `MIRI_LOG`. + // (FIXME: use `var_os`, but then we need to manually concatenate instead of `format!`.) + if let Ok(var) = env::var("MIRI_LOG") { + if env::var_os("RUSTC_LOG").is_none() { + // We try to be a bit clever here: if `MIRI_LOG` is just a single level + // used for everything, we only apply it to the parts of rustc that are + // CTFE-related. Otherwise, we use it verbatim for `RUSTC_LOG`. + // This way, if you set `MIRI_LOG=trace`, you get only the right parts of + // rustc traced, but you can also do `MIRI_LOG=miri=trace,rustc_const_eval::interpret=debug`. + if log::Level::from_str(&var).is_ok() { + env::set_var( + "RUSTC_LOG", + &format!( + "rustc_middle::mir::interpret={0},rustc_const_eval::interpret={0}", + var + ), + ); + } else { + env::set_var("RUSTC_LOG", &var); + } + rustc_driver::init_rustc_env_logger(); + } + } + + // If `MIRI_BACKTRACE` is set and `RUSTC_CTFE_BACKTRACE` is not, set `RUSTC_CTFE_BACKTRACE`. + // Do this late, so we ideally only apply this to Miri's errors. + if let Some(val) = env::var_os("MIRI_BACKTRACE") { + let ctfe_backtrace = match &*val.to_string_lossy() { + "immediate" => CtfeBacktrace::Immediate, + "0" => CtfeBacktrace::Disabled, + _ => CtfeBacktrace::Capture, + }; + *tcx.sess.ctfe_backtrace.borrow_mut() = ctfe_backtrace; + } +} + +/// Returns the "default sysroot" that Miri will use for host things if no `--sysroot` flag is set. +/// Should be a compile-time constant. +fn host_sysroot() -> Option { + if option_env!("RUSTC_STAGE").is_some() { + // This is being built as part of rustc, and gets shipped with rustup. + // We can rely on the sysroot computation in librustc_session. + return None; + } + // For builds outside rustc, we need to ensure that we got a sysroot + // that gets used as a default. The sysroot computation in librustc_session would + // end up somewhere in the build dir (see `get_or_default_sysroot`). + // Taken from PR . + let home = option_env!("RUSTUP_HOME").or(option_env!("MULTIRUST_HOME")); + let toolchain = option_env!("RUSTUP_TOOLCHAIN").or(option_env!("MULTIRUST_TOOLCHAIN")); + Some(match (home, toolchain) { + (Some(home), Some(toolchain)) => { + // Check that at runtime, we are still in this toolchain (if there is any toolchain). + if let Some(toolchain_runtime) = + env::var_os("RUSTUP_TOOLCHAIN").or_else(|| env::var_os("MULTIRUST_TOOLCHAIN")) + { + if toolchain_runtime != toolchain { + show_error!( + "This Miri got built with local toolchain `{toolchain}`, but now is being run under a different toolchain. \n\ + Make sure to run Miri in the toolchain it got built with, e.g. via `cargo +{toolchain} miri`." + ) + } + } + format!("{}/toolchains/{}", home, toolchain) + } + _ => option_env!("RUST_SYSROOT") + .unwrap_or_else(|| { + show_error!( + "To build Miri without rustup, set the `RUST_SYSROOT` env var at build time", + ) + }) + .to_owned(), + }) +} + +/// Execute a compiler with the given CLI arguments and callbacks. +fn run_compiler( + mut args: Vec, + target_crate: bool, + callbacks: &mut (dyn rustc_driver::Callbacks + Send), +) -> ! { + // Make sure we use the right default sysroot. The default sysroot is wrong, + // because `get_or_default_sysroot` in `librustc_session` bases that on `current_exe`. + // + // Make sure we always call `host_sysroot` as that also does some sanity-checks + // of the environment we were built in and whether it matches what we are running in. + let host_default_sysroot = host_sysroot(); + // Now see if we even need to set something. + let sysroot_flag = "--sysroot"; + if !args.iter().any(|e| e == sysroot_flag) { + // No sysroot was set, let's see if we have a custom default we want to configure. + let default_sysroot = if target_crate { + // Using the built-in default here would be plain wrong, so we *require* + // the env var to make sure things make sense. + Some(env::var("MIRI_SYSROOT").unwrap_or_else(|_| { + show_error!( + "Miri was invoked in 'target' mode without `MIRI_SYSROOT` or `--sysroot` being set" + ) + })) + } else { + host_default_sysroot + }; + if let Some(sysroot) = default_sysroot { + // We need to overwrite the default that librustc_session would compute. + args.push(sysroot_flag.to_owned()); + args.push(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`: + // https://github.com/rust-lang/miri/issues/1760 + if target_crate { + // Some options have different defaults in Miri than in plain rustc; apply those by making + // them the first arguments after the binary name (but later arguments can overwrite them). + args.splice(1..1, miri::MIRI_DEFAULT_ARGS.iter().map(ToString::to_string)); + } + + // Invoke compiler, and handle return code. + let exit_code = rustc_driver::catch_with_exit_code(move || { + rustc_driver::RunCompiler::new(&args, callbacks).run() + }); + std::process::exit(exit_code) +} + +/// Parses a comma separated list of `T` from the given string: +/// +/// `,,,...` +fn parse_comma_list(input: &str) -> Result, T::Err> { + input.split(',').map(str::parse::).collect() +} + +fn main() { + // Snapshot a copy of the environment before `rustc` starts messing with it. + // (`install_ice_hook` might change `RUST_BACKTRACE`.) + let env_snapshot = env::vars_os().collect::>(); + + // Earliest rustc setup. + rustc_driver::install_ice_hook(); + + // If the environment asks us to actually be rustc, then do that. + if let Some(crate_kind) = env::var_os("MIRI_BE_RUSTC") { + rustc_driver::init_rustc_env_logger(); + + let target_crate = if crate_kind == "target" { + true + } else if crate_kind == "host" { + false + } else { + panic!("invalid `MIRI_BE_RUSTC` value: {:?}", crate_kind) + }; + + // We cannot use `rustc_driver::main` as we need to adjust the CLI arguments. + run_compiler( + env::args().collect(), + target_crate, + &mut MiriBeRustCompilerCalls { target_crate }, + ) + } + + // Init loggers the Miri way. + init_early_loggers(); + + // Parse our arguments and split them across `rustc` and `miri`. + let mut miri_config = miri::MiriConfig::default(); + miri_config.env = env_snapshot; + + let mut rustc_args = vec![]; + let mut after_dashdash = false; + + // If user has explicitly enabled/disabled isolation + let mut isolation_enabled: Option = None; + for arg in env::args() { + if rustc_args.is_empty() { + // Very first arg: binary name. + rustc_args.push(arg); + } else if after_dashdash { + // Everything that comes after `--` is forwarded to the interpreted crate. + miri_config.args.push(arg); + } else if arg == "--" { + after_dashdash = true; + } else if arg == "-Zmiri-disable-validation" { + miri_config.validate = false; + } else if arg == "-Zmiri-disable-stacked-borrows" { + miri_config.stacked_borrows = false; + } else if arg == "-Zmiri-disable-data-race-detector" { + miri_config.data_race_detector = false; + miri_config.weak_memory_emulation = false; + } else if arg == "-Zmiri-disable-alignment-check" { + miri_config.check_alignment = miri::AlignmentCheck::None; + } else if arg == "-Zmiri-symbolic-alignment-check" { + miri_config.check_alignment = miri::AlignmentCheck::Symbolic; + } else if arg == "-Zmiri-check-number-validity" { + eprintln!( + "WARNING: the flag `-Zmiri-check-number-validity` no longer has any effect \ + since it is now enabled by default" + ); + } else if arg == "-Zmiri-disable-abi-check" { + miri_config.check_abi = false; + } else if arg == "-Zmiri-disable-isolation" { + if matches!(isolation_enabled, Some(true)) { + show_error!( + "-Zmiri-disable-isolation cannot be used along with -Zmiri-isolation-error" + ); + } else { + isolation_enabled = Some(false); + } + miri_config.isolated_op = miri::IsolatedOp::Allow; + } else if arg == "-Zmiri-disable-weak-memory-emulation" { + miri_config.weak_memory_emulation = false; + } else if arg == "-Zmiri-track-weak-memory-loads" { + miri_config.track_outdated_loads = true; + } else if let Some(param) = arg.strip_prefix("-Zmiri-isolation-error=") { + if matches!(isolation_enabled, Some(false)) { + show_error!( + "-Zmiri-isolation-error cannot be used along with -Zmiri-disable-isolation" + ); + } else { + isolation_enabled = Some(true); + } + + miri_config.isolated_op = match param { + "abort" => miri::IsolatedOp::Reject(miri::RejectOpWith::Abort), + "hide" => miri::IsolatedOp::Reject(miri::RejectOpWith::NoWarning), + "warn" => miri::IsolatedOp::Reject(miri::RejectOpWith::Warning), + "warn-nobacktrace" => + miri::IsolatedOp::Reject(miri::RejectOpWith::WarningWithoutBacktrace), + _ => + show_error!( + "-Zmiri-isolation-error must be `abort`, `hide`, `warn`, or `warn-nobacktrace`" + ), + }; + } else if arg == "-Zmiri-ignore-leaks" { + miri_config.ignore_leaks = true; + } else if arg == "-Zmiri-panic-on-unsupported" { + miri_config.panic_on_unsupported = true; + } else if arg == "-Zmiri-tag-raw-pointers" { + eprintln!("WARNING: `-Zmiri-tag-raw-pointers` has no effect; it is enabled by default"); + } else if arg == "-Zmiri-strict-provenance" { + miri_config.provenance_mode = ProvenanceMode::Strict; + } else if arg == "-Zmiri-permissive-provenance" { + miri_config.provenance_mode = ProvenanceMode::Permissive; + } else if arg == "-Zmiri-mute-stdout-stderr" { + miri_config.mute_stdout_stderr = true; + } else if arg == "-Zmiri-retag-fields" { + miri_config.retag_fields = true; + } else if arg == "-Zmiri-track-raw-pointers" { + eprintln!( + "WARNING: `-Zmiri-track-raw-pointers` has no effect; it is enabled by default" + ); + } else if let Some(param) = arg.strip_prefix("-Zmiri-seed=") { + if miri_config.seed.is_some() { + show_error!("Cannot specify -Zmiri-seed multiple times!"); + } + let seed = u64::from_str_radix(param, 16) + .unwrap_or_else(|_| show_error!( + "-Zmiri-seed should only contain valid hex digits [0-9a-fA-F] and must fit into a u64 (max 16 characters)" + )); + miri_config.seed = Some(seed); + } else if let Some(_param) = arg.strip_prefix("-Zmiri-env-exclude=") { + show_error!( + "`-Zmiri-env-exclude` has been removed; unset env vars before starting Miri instead" + ); + } else if let Some(param) = arg.strip_prefix("-Zmiri-env-forward=") { + miri_config.forwarded_env_vars.push(param.to_owned()); + } else if let Some(param) = arg.strip_prefix("-Zmiri-track-pointer-tag=") { + let ids: Vec = match parse_comma_list(param) { + Ok(ids) => ids, + Err(err) => + show_error!( + "-Zmiri-track-pointer-tag requires a comma separated list of valid `u64` arguments: {}", + err + ), + }; + for id in ids.into_iter().map(miri::SbTag::new) { + if let Some(id) = id { + miri_config.tracked_pointer_tags.insert(id); + } else { + show_error!("-Zmiri-track-pointer-tag requires nonzero arguments"); + } + } + } else if let Some(param) = arg.strip_prefix("-Zmiri-track-call-id=") { + let ids: Vec = match parse_comma_list(param) { + Ok(ids) => ids, + Err(err) => + show_error!( + "-Zmiri-track-call-id requires a comma separated list of valid `u64` arguments: {}", + err + ), + }; + for id in ids.into_iter().map(miri::CallId::new) { + if let Some(id) = id { + miri_config.tracked_call_ids.insert(id); + } else { + show_error!("-Zmiri-track-call-id requires a nonzero argument"); + } + } + } else if let Some(param) = arg.strip_prefix("-Zmiri-track-alloc-id=") { + let ids: Vec = match parse_comma_list::(param) { + Ok(ids) => ids.into_iter().map(miri::AllocId).collect(), + Err(err) => + show_error!( + "-Zmiri-track-alloc-id requires a comma separated list of valid non-zero `u64` arguments: {}", + err + ), + }; + miri_config.tracked_alloc_ids.extend(ids); + } else if let Some(param) = arg.strip_prefix("-Zmiri-compare-exchange-weak-failure-rate=") { + let rate = match param.parse::() { + Ok(rate) if rate >= 0.0 && rate <= 1.0 => rate, + Ok(_) => + show_error!( + "-Zmiri-compare-exchange-weak-failure-rate must be between `0.0` and `1.0`" + ), + Err(err) => + show_error!( + "-Zmiri-compare-exchange-weak-failure-rate requires a `f64` between `0.0` and `1.0`: {}", + err + ), + }; + miri_config.cmpxchg_weak_failure_rate = rate; + } else if let Some(param) = arg.strip_prefix("-Zmiri-preemption-rate=") { + let rate = match param.parse::() { + Ok(rate) if rate >= 0.0 && rate <= 1.0 => rate, + Ok(_) => show_error!("-Zmiri-preemption-rate must be between `0.0` and `1.0`"), + Err(err) => + show_error!( + "-Zmiri-preemption-rate requires a `f64` between `0.0` and `1.0`: {}", + err + ), + }; + miri_config.preemption_rate = rate; + } else if arg == "-Zmiri-report-progress" { + // This makes it take a few seconds between progress reports on my laptop. + miri_config.report_progress = Some(1_000_000); + } else if let Some(param) = arg.strip_prefix("-Zmiri-report-progress=") { + let interval = match param.parse::() { + Ok(i) => i, + Err(err) => show_error!("-Zmiri-report-progress requires a `u32`: {}", err), + }; + miri_config.report_progress = Some(interval); + } else if let Some(param) = arg.strip_prefix("-Zmiri-tag-gc=") { + let interval = match param.parse::() { + Ok(i) => i, + Err(err) => show_error!("-Zmiri-tag-gc requires a `u32`: {}", err), + }; + miri_config.gc_interval = interval; + } else if let Some(param) = arg.strip_prefix("-Zmiri-measureme=") { + miri_config.measureme_out = Some(param.to_string()); + } else if let Some(param) = arg.strip_prefix("-Zmiri-backtrace=") { + miri_config.backtrace_style = match param { + "0" => BacktraceStyle::Off, + "1" => BacktraceStyle::Short, + "full" => BacktraceStyle::Full, + _ => show_error!("-Zmiri-backtrace may only be 0, 1, or full"), + }; + } else if let Some(param) = arg.strip_prefix("-Zmiri-extern-so-file=") { + let filename = param.to_string(); + if std::path::Path::new(&filename).exists() { + if let Some(other_filename) = miri_config.external_so_file { + show_error!( + "-Zmiri-extern-so-file is already set to {}", + other_filename.display() + ); + } + miri_config.external_so_file = Some(filename.into()); + } else { + show_error!("-Zmiri-extern-so-file `{}` does not exist", filename); + } + } else { + // Forward to rustc. + rustc_args.push(arg); + } + } + + debug!("rustc arguments: {:?}", rustc_args); + debug!("crate arguments: {:?}", miri_config.args); + run_compiler(rustc_args, /* target_crate: */ true, &mut MiriCompilerCalls { miri_config }) +} diff --git a/src/tools/miri/src/clock.rs b/src/tools/miri/src/clock.rs new file mode 100644 index 0000000000000..3f33273e1e541 --- /dev/null +++ b/src/tools/miri/src/clock.rs @@ -0,0 +1,115 @@ +use std::sync::atomic::{AtomicU64, Ordering}; +use std::time::{Duration, Instant as StdInstant}; + +/// When using a virtual clock, this defines how many nanoseconds we pretend are passing for each +/// basic block. +const NANOSECONDS_PER_BASIC_BLOCK: u64 = 10; + +#[derive(Debug)] +pub struct Instant { + kind: InstantKind, +} + +#[derive(Debug)] +enum InstantKind { + Host(StdInstant), + Virtual { nanoseconds: u64 }, +} + +impl Instant { + pub fn checked_add(&self, duration: Duration) -> Option { + match self.kind { + InstantKind::Host(instant) => + instant.checked_add(duration).map(|i| Instant { kind: InstantKind::Host(i) }), + InstantKind::Virtual { nanoseconds } => + u128::from(nanoseconds) + .checked_add(duration.as_nanos()) + .and_then(|n| u64::try_from(n).ok()) + .map(|nanoseconds| Instant { kind: InstantKind::Virtual { nanoseconds } }), + } + } + + pub fn duration_since(&self, earlier: Instant) -> Duration { + match (&self.kind, earlier.kind) { + (InstantKind::Host(instant), InstantKind::Host(earlier)) => + instant.duration_since(earlier), + ( + InstantKind::Virtual { nanoseconds }, + InstantKind::Virtual { nanoseconds: earlier }, + ) => Duration::from_nanos(nanoseconds.saturating_sub(earlier)), + _ => panic!("all `Instant` must be of the same kind"), + } + } +} + +/// A monotone clock used for `Instant` simulation. +#[derive(Debug)] +pub struct Clock { + kind: ClockKind, +} + +#[derive(Debug)] +enum ClockKind { + Host { + /// The "time anchor" for this machine's monotone clock. + time_anchor: StdInstant, + }, + Virtual { + /// The "current virtual time". + nanoseconds: AtomicU64, + }, +} + +impl Clock { + /// Create a new clock based on the availability of communication with the host. + pub fn new(communicate: bool) -> Self { + let kind = if communicate { + ClockKind::Host { time_anchor: StdInstant::now() } + } else { + ClockKind::Virtual { nanoseconds: 0.into() } + }; + + Self { kind } + } + + /// Let the time pass for a small interval. + pub fn tick(&self) { + match &self.kind { + ClockKind::Host { .. } => { + // Time will pass without us doing anything. + } + ClockKind::Virtual { nanoseconds } => { + nanoseconds.fetch_add(NANOSECONDS_PER_BASIC_BLOCK, Ordering::SeqCst); + } + } + } + + /// Sleep for the desired duration. + pub fn sleep(&self, duration: Duration) { + match &self.kind { + ClockKind::Host { .. } => std::thread::sleep(duration), + ClockKind::Virtual { nanoseconds } => { + // Just pretend that we have slept for some time. + nanoseconds.fetch_add(duration.as_nanos().try_into().unwrap(), Ordering::SeqCst); + } + } + } + + /// Return the `anchor` instant, to convert between monotone instants and durations relative to the anchor. + pub fn anchor(&self) -> Instant { + match &self.kind { + ClockKind::Host { time_anchor } => Instant { kind: InstantKind::Host(*time_anchor) }, + ClockKind::Virtual { .. } => Instant { kind: InstantKind::Virtual { nanoseconds: 0 } }, + } + } + + pub fn now(&self) -> Instant { + match &self.kind { + ClockKind::Host { .. } => Instant { kind: InstantKind::Host(StdInstant::now()) }, + ClockKind::Virtual { nanoseconds } => + Instant { + kind: InstantKind::Virtual { nanoseconds: nanoseconds.load(Ordering::SeqCst) }, + }, + } + } +} diff --git a/src/tools/miri/src/concurrency/data_race.rs b/src/tools/miri/src/concurrency/data_race.rs new file mode 100644 index 0000000000000..2e54ddaaba113 --- /dev/null +++ b/src/tools/miri/src/concurrency/data_race.rs @@ -0,0 +1,1596 @@ +//! Implementation of a data-race detector using Lamport Timestamps / Vector-clocks +//! based on the Dynamic Race Detection for C++: +//! +//! which does not report false-positives when fences are used, and gives better +//! accuracy in presence of read-modify-write operations. +//! +//! The implementation contains modifications to correctly model the changes to the memory model in C++20 +//! regarding the weakening of release sequences: . +//! Relaxed stores now unconditionally block all currently active release sequences and so per-thread tracking of release +//! sequences is not needed. +//! +//! The implementation also models races with memory allocation and deallocation via treating allocation and +//! deallocation as a type of write internally for detecting data-races. +//! +//! Weak memory orders are explored but not all weak behaviours are exhibited, so it can still miss data-races +//! but should not report false-positives +//! +//! Data-race definition from(): +//! a data race occurs between two memory accesses if they are on different threads, at least one operation +//! is non-atomic, at least one operation is a write and neither access happens-before the other. Read the link +//! for full definition. +//! +//! This re-uses vector indexes for threads that are known to be unable to report data-races, this is valid +//! because it only re-uses vector indexes once all currently-active (not-terminated) threads have an internal +//! vector clock that happens-after the join operation of the candidate thread. Threads that have not been joined +//! on are not considered. Since the thread's vector clock will only increase and a data-race implies that +//! there is some index x where `clock[x] > thread_clock`, when this is true `clock[candidate-idx] > thread_clock` +//! can never hold and hence a data-race can never be reported in that vector index again. +//! This means that the thread-index can be safely re-used, starting on the next timestamp for the newly created +//! thread. +//! +//! The timestamps used in the data-race detector assign each sequence of non-atomic operations +//! followed by a single atomic or concurrent operation a single timestamp. +//! Write, Read, Write, ThreadJoin will be represented by a single timestamp value on a thread. +//! This is because extra increment operations between the operations in the sequence are not +//! required for accurate reporting of data-race values. +//! +//! As per the paper a threads timestamp is only incremented after a release operation is performed +//! so some atomic operations that only perform acquires do not increment the timestamp. Due to shared +//! code some atomic operations may increment the timestamp when not necessary but this has no effect +//! on the data-race detection code. + +use std::{ + cell::{Cell, Ref, RefCell, RefMut}, + fmt::Debug, + mem, +}; + +use rustc_ast::Mutability; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_index::vec::{Idx, IndexVec}; +use rustc_middle::{mir, ty::layout::TyAndLayout}; +use rustc_target::abi::{Align, Size}; + +use crate::*; + +use super::{ + vector_clock::{VClock, VTimestamp, VectorIdx}, + weak_memory::EvalContextExt as _, +}; + +pub type AllocExtra = VClockAlloc; + +/// Valid atomic read-write orderings, alias of atomic::Ordering (not non-exhaustive). +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum AtomicRwOrd { + Relaxed, + Acquire, + Release, + AcqRel, + SeqCst, +} + +/// Valid atomic read orderings, subset of atomic::Ordering. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum AtomicReadOrd { + Relaxed, + Acquire, + SeqCst, +} + +/// Valid atomic write orderings, subset of atomic::Ordering. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum AtomicWriteOrd { + Relaxed, + Release, + SeqCst, +} + +/// Valid atomic fence orderings, subset of atomic::Ordering. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum AtomicFenceOrd { + Acquire, + Release, + AcqRel, + SeqCst, +} + +/// The current set of vector clocks describing the state +/// of a thread, contains the happens-before clock and +/// additional metadata to model atomic fence operations. +#[derive(Clone, Default, Debug)] +pub(super) struct ThreadClockSet { + /// The increasing clock representing timestamps + /// that happen-before this thread. + pub(super) clock: VClock, + + /// The set of timestamps that will happen-before this + /// thread once it performs an acquire fence. + fence_acquire: VClock, + + /// The last timestamp of happens-before relations that + /// have been released by this thread by a fence. + fence_release: VClock, + + /// Timestamps of the last SC fence performed by each + /// thread, updated when this thread performs an SC fence + pub(super) fence_seqcst: VClock, + + /// Timestamps of the last SC write performed by each + /// thread, updated when this thread performs an SC fence + pub(super) write_seqcst: VClock, + + /// Timestamps of the last SC fence performed by each + /// thread, updated when this thread performs an SC read + pub(super) read_seqcst: VClock, +} + +impl ThreadClockSet { + /// Apply the effects of a release fence to this + /// set of thread vector clocks. + #[inline] + fn apply_release_fence(&mut self) { + self.fence_release.clone_from(&self.clock); + } + + /// Apply the effects of an acquire fence to this + /// set of thread vector clocks. + #[inline] + fn apply_acquire_fence(&mut self) { + self.clock.join(&self.fence_acquire); + } + + /// Increment the happens-before clock at a + /// known index. + #[inline] + fn increment_clock(&mut self, index: VectorIdx) { + self.clock.increment_index(index); + } + + /// Join the happens-before clock with that of + /// another thread, used to model thread join + /// operations. + fn join_with(&mut self, other: &ThreadClockSet) { + self.clock.join(&other.clock); + } +} + +/// Error returned by finding a data race +/// should be elaborated upon. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub struct DataRace; + +/// Externally stored memory cell clocks +/// explicitly to reduce memory usage for the +/// common case where no atomic operations +/// exists on the memory cell. +#[derive(Clone, PartialEq, Eq, Default, Debug)] +struct AtomicMemoryCellClocks { + /// The clock-vector of the timestamp of the last atomic + /// read operation performed by each thread. + /// This detects potential data-races between atomic read + /// and non-atomic write operations. + read_vector: VClock, + + /// The clock-vector of the timestamp of the last atomic + /// write operation performed by each thread. + /// This detects potential data-races between atomic write + /// and non-atomic read or write operations. + write_vector: VClock, + + /// Synchronization vector for acquire-release semantics + /// contains the vector of timestamps that will + /// happen-before a thread if an acquire-load is + /// performed on the data. + sync_vector: VClock, +} + +/// Type of write operation: allocating memory +/// non-atomic writes and deallocating memory +/// are all treated as writes for the purpose +/// of the data-race detector. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +enum WriteType { + /// Allocate memory. + Allocate, + + /// Standard unsynchronized write. + Write, + + /// Deallocate memory. + /// Note that when memory is deallocated first, later non-atomic accesses + /// will be reported as use-after-free, not as data races. + /// (Same for `Allocate` above.) + Deallocate, +} +impl WriteType { + fn get_descriptor(self) -> &'static str { + match self { + WriteType::Allocate => "Allocate", + WriteType::Write => "Write", + WriteType::Deallocate => "Deallocate", + } + } +} + +/// Memory Cell vector clock metadata +/// for data-race detection. +#[derive(Clone, PartialEq, Eq, Debug)] +struct MemoryCellClocks { + /// The vector-clock timestamp of the last write + /// corresponding to the writing threads timestamp. + write: VTimestamp, + + /// The identifier of the vector index, corresponding to a thread + /// that performed the last write operation. + write_index: VectorIdx, + + /// The type of operation that the write index represents, + /// either newly allocated memory, a non-atomic write or + /// a deallocation of memory. + write_type: WriteType, + + /// The vector-clock of the timestamp of the last read operation + /// performed by a thread since the last write operation occurred. + /// It is reset to zero on each write operation. + read: VClock, + + /// Atomic acquire & release sequence tracking clocks. + /// For non-atomic memory in the common case this + /// value is set to None. + atomic_ops: Option>, +} + +impl MemoryCellClocks { + /// Create a new set of clocks representing memory allocated + /// at a given vector timestamp and index. + fn new(alloc: VTimestamp, alloc_index: VectorIdx) -> Self { + MemoryCellClocks { + read: VClock::default(), + write: alloc, + write_index: alloc_index, + write_type: WriteType::Allocate, + atomic_ops: None, + } + } + + /// Load the internal atomic memory cells if they exist. + #[inline] + fn atomic(&self) -> Option<&AtomicMemoryCellClocks> { + self.atomic_ops.as_deref() + } + + /// Load or create the internal atomic memory metadata + /// if it does not exist. + #[inline] + fn atomic_mut(&mut self) -> &mut AtomicMemoryCellClocks { + self.atomic_ops.get_or_insert_with(Default::default) + } + + /// Update memory cell data-race tracking for atomic + /// load acquire semantics, is a no-op if this memory was + /// not used previously as atomic memory. + fn load_acquire( + &mut self, + clocks: &mut ThreadClockSet, + index: VectorIdx, + ) -> Result<(), DataRace> { + self.atomic_read_detect(clocks, index)?; + if let Some(atomic) = self.atomic() { + clocks.clock.join(&atomic.sync_vector); + } + Ok(()) + } + + /// Checks if the memory cell access is ordered with all prior atomic reads and writes + fn race_free_with_atomic(&self, clocks: &ThreadClockSet) -> bool { + if let Some(atomic) = self.atomic() { + atomic.read_vector <= clocks.clock && atomic.write_vector <= clocks.clock + } else { + true + } + } + + /// Update memory cell data-race tracking for atomic + /// load relaxed semantics, is a no-op if this memory was + /// not used previously as atomic memory. + fn load_relaxed( + &mut self, + clocks: &mut ThreadClockSet, + index: VectorIdx, + ) -> Result<(), DataRace> { + self.atomic_read_detect(clocks, index)?; + if let Some(atomic) = self.atomic() { + clocks.fence_acquire.join(&atomic.sync_vector); + } + Ok(()) + } + + /// Update the memory cell data-race tracking for atomic + /// store release semantics. + fn store_release(&mut self, clocks: &ThreadClockSet, index: VectorIdx) -> Result<(), DataRace> { + self.atomic_write_detect(clocks, index)?; + let atomic = self.atomic_mut(); + atomic.sync_vector.clone_from(&clocks.clock); + Ok(()) + } + + /// Update the memory cell data-race tracking for atomic + /// store relaxed semantics. + fn store_relaxed(&mut self, clocks: &ThreadClockSet, index: VectorIdx) -> Result<(), DataRace> { + self.atomic_write_detect(clocks, index)?; + + // The handling of release sequences was changed in C++20 and so + // the code here is different to the paper since now all relaxed + // stores block release sequences. The exception for same-thread + // relaxed stores has been removed. + let atomic = self.atomic_mut(); + atomic.sync_vector.clone_from(&clocks.fence_release); + Ok(()) + } + + /// Update the memory cell data-race tracking for atomic + /// store release semantics for RMW operations. + fn rmw_release(&mut self, clocks: &ThreadClockSet, index: VectorIdx) -> Result<(), DataRace> { + self.atomic_write_detect(clocks, index)?; + let atomic = self.atomic_mut(); + atomic.sync_vector.join(&clocks.clock); + Ok(()) + } + + /// Update the memory cell data-race tracking for atomic + /// store relaxed semantics for RMW operations. + fn rmw_relaxed(&mut self, clocks: &ThreadClockSet, index: VectorIdx) -> Result<(), DataRace> { + self.atomic_write_detect(clocks, index)?; + let atomic = self.atomic_mut(); + atomic.sync_vector.join(&clocks.fence_release); + Ok(()) + } + + /// Detect data-races with an atomic read, caused by a non-atomic write that does + /// not happen-before the atomic-read. + fn atomic_read_detect( + &mut self, + clocks: &ThreadClockSet, + index: VectorIdx, + ) -> Result<(), DataRace> { + log::trace!("Atomic read with vectors: {:#?} :: {:#?}", self, clocks); + if self.write <= clocks.clock[self.write_index] { + let atomic = self.atomic_mut(); + atomic.read_vector.set_at_index(&clocks.clock, index); + Ok(()) + } else { + Err(DataRace) + } + } + + /// Detect data-races with an atomic write, either with a non-atomic read or with + /// a non-atomic write. + fn atomic_write_detect( + &mut self, + clocks: &ThreadClockSet, + index: VectorIdx, + ) -> Result<(), DataRace> { + log::trace!("Atomic write with vectors: {:#?} :: {:#?}", self, clocks); + if self.write <= clocks.clock[self.write_index] && self.read <= clocks.clock { + let atomic = self.atomic_mut(); + atomic.write_vector.set_at_index(&clocks.clock, index); + Ok(()) + } else { + Err(DataRace) + } + } + + /// Detect races for non-atomic read operations at the current memory cell + /// returns true if a data-race is detected. + fn read_race_detect( + &mut self, + clocks: &ThreadClockSet, + index: VectorIdx, + ) -> Result<(), DataRace> { + log::trace!("Unsynchronized read with vectors: {:#?} :: {:#?}", self, clocks); + if self.write <= clocks.clock[self.write_index] { + let race_free = if let Some(atomic) = self.atomic() { + atomic.write_vector <= clocks.clock + } else { + true + }; + if race_free { + self.read.set_at_index(&clocks.clock, index); + Ok(()) + } else { + Err(DataRace) + } + } else { + Err(DataRace) + } + } + + /// Detect races for non-atomic write operations at the current memory cell + /// returns true if a data-race is detected. + fn write_race_detect( + &mut self, + clocks: &ThreadClockSet, + index: VectorIdx, + write_type: WriteType, + ) -> Result<(), DataRace> { + log::trace!("Unsynchronized write with vectors: {:#?} :: {:#?}", self, clocks); + if self.write <= clocks.clock[self.write_index] && self.read <= clocks.clock { + let race_free = if let Some(atomic) = self.atomic() { + atomic.write_vector <= clocks.clock && atomic.read_vector <= clocks.clock + } else { + true + }; + if race_free { + self.write = clocks.clock[index]; + self.write_index = index; + self.write_type = write_type; + self.read.set_zero_vector(); + Ok(()) + } else { + Err(DataRace) + } + } else { + Err(DataRace) + } + } +} + +/// Evaluation context extensions. +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for MiriInterpCx<'mir, 'tcx> {} +pub trait EvalContextExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> { + /// Atomic variant of read_scalar_at_offset. + fn read_scalar_at_offset_atomic( + &self, + op: &OpTy<'tcx, Provenance>, + offset: u64, + layout: TyAndLayout<'tcx>, + atomic: AtomicReadOrd, + ) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_ref(); + let value_place = this.deref_operand_and_offset(op, offset, layout)?; + this.read_scalar_atomic(&value_place, atomic) + } + + /// Atomic variant of write_scalar_at_offset. + fn write_scalar_at_offset_atomic( + &mut self, + op: &OpTy<'tcx, Provenance>, + offset: u64, + value: impl Into>, + layout: TyAndLayout<'tcx>, + atomic: AtomicWriteOrd, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + let value_place = this.deref_operand_and_offset(op, offset, layout)?; + this.write_scalar_atomic(value.into(), &value_place, atomic) + } + + /// Perform an atomic read operation at the memory location. + fn read_scalar_atomic( + &self, + place: &MPlaceTy<'tcx, Provenance>, + atomic: AtomicReadOrd, + ) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_ref(); + this.atomic_access_check(place)?; + // This will read from the last store in the modification order of this location. In case + // weak memory emulation is enabled, this may not be the store we will pick to actually read from and return. + // This is fine with StackedBorrow and race checks because they don't concern metadata on + // the *value* (including the associated provenance if this is an AtomicPtr) at this location. + // Only metadata on the location itself is used. + let scalar = this.allow_data_races_ref(move |this| this.read_scalar(&place.into()))?; + this.validate_overlapping_atomic(place)?; + this.buffered_atomic_read(place, atomic, scalar, || { + this.validate_atomic_load(place, atomic) + }) + } + + /// Perform an atomic write operation at the memory location. + fn write_scalar_atomic( + &mut self, + val: Scalar, + dest: &MPlaceTy<'tcx, Provenance>, + atomic: AtomicWriteOrd, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + this.atomic_access_check(dest)?; + + this.validate_overlapping_atomic(dest)?; + this.allow_data_races_mut(move |this| this.write_scalar(val, &dest.into()))?; + this.validate_atomic_store(dest, atomic)?; + // FIXME: it's not possible to get the value before write_scalar. A read_scalar will cause + // side effects from a read the program did not perform. So we have to initialise + // the store buffer with the value currently being written + // ONCE this is fixed please remove the hack in buffered_atomic_write() in weak_memory.rs + // https://github.com/rust-lang/miri/issues/2164 + this.buffered_atomic_write(val, dest, atomic, val) + } + + /// Perform an atomic operation on a memory location. + fn atomic_op_immediate( + &mut self, + place: &MPlaceTy<'tcx, Provenance>, + rhs: &ImmTy<'tcx, Provenance>, + op: mir::BinOp, + neg: bool, + atomic: AtomicRwOrd, + ) -> InterpResult<'tcx, ImmTy<'tcx, Provenance>> { + let this = self.eval_context_mut(); + this.atomic_access_check(place)?; + + this.validate_overlapping_atomic(place)?; + let old = this.allow_data_races_mut(|this| this.read_immediate(&place.into()))?; + + // Atomics wrap around on overflow. + let val = this.binary_op(op, &old, rhs)?; + let val = if neg { this.unary_op(mir::UnOp::Not, &val)? } else { val }; + this.allow_data_races_mut(|this| this.write_immediate(*val, &place.into()))?; + + this.validate_atomic_rmw(place, atomic)?; + + this.buffered_atomic_rmw(val.to_scalar(), place, atomic, old.to_scalar())?; + Ok(old) + } + + /// Perform an atomic exchange with a memory place and a new + /// scalar value, the old value is returned. + fn atomic_exchange_scalar( + &mut self, + place: &MPlaceTy<'tcx, Provenance>, + new: Scalar, + atomic: AtomicRwOrd, + ) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + this.atomic_access_check(place)?; + + this.validate_overlapping_atomic(place)?; + let old = this.allow_data_races_mut(|this| this.read_scalar(&place.into()))?; + this.allow_data_races_mut(|this| this.write_scalar(new, &place.into()))?; + + this.validate_atomic_rmw(place, atomic)?; + + this.buffered_atomic_rmw(new, place, atomic, old)?; + Ok(old) + } + + /// Perform an conditional atomic exchange with a memory place and a new + /// scalar value, the old value is returned. + fn atomic_min_max_scalar( + &mut self, + place: &MPlaceTy<'tcx, Provenance>, + rhs: ImmTy<'tcx, Provenance>, + min: bool, + atomic: AtomicRwOrd, + ) -> InterpResult<'tcx, ImmTy<'tcx, Provenance>> { + let this = self.eval_context_mut(); + this.atomic_access_check(place)?; + + this.validate_overlapping_atomic(place)?; + let old = this.allow_data_races_mut(|this| this.read_immediate(&place.into()))?; + let lt = this.binary_op(mir::BinOp::Lt, &old, &rhs)?.to_scalar().to_bool()?; + + let new_val = if min { + if lt { &old } else { &rhs } + } else { + if lt { &rhs } else { &old } + }; + + this.allow_data_races_mut(|this| this.write_immediate(**new_val, &place.into()))?; + + this.validate_atomic_rmw(place, atomic)?; + + this.buffered_atomic_rmw(new_val.to_scalar(), place, atomic, old.to_scalar())?; + + // Return the old value. + Ok(old) + } + + /// Perform an atomic compare and exchange at a given memory location. + /// On success an atomic RMW operation is performed and on failure + /// only an atomic read occurs. If `can_fail_spuriously` is true, + /// then we treat it as a "compare_exchange_weak" operation, and + /// some portion of the time fail even when the values are actually + /// identical. + fn atomic_compare_exchange_scalar( + &mut self, + place: &MPlaceTy<'tcx, Provenance>, + expect_old: &ImmTy<'tcx, Provenance>, + new: Scalar, + success: AtomicRwOrd, + fail: AtomicReadOrd, + can_fail_spuriously: bool, + ) -> InterpResult<'tcx, Immediate> { + use rand::Rng as _; + let this = self.eval_context_mut(); + this.atomic_access_check(place)?; + + this.validate_overlapping_atomic(place)?; + // Failure ordering cannot be stronger than success ordering, therefore first attempt + // to read with the failure ordering and if successful then try again with the success + // read ordering and write in the success case. + // Read as immediate for the sake of `binary_op()` + let old = this.allow_data_races_mut(|this| this.read_immediate(&(place.into())))?; + // `binary_op` will bail if either of them is not a scalar. + let eq = this.binary_op(mir::BinOp::Eq, &old, expect_old)?; + // If the operation would succeed, but is "weak", fail some portion + // of the time, based on `success_rate`. + let success_rate = 1.0 - this.machine.cmpxchg_weak_failure_rate; + let cmpxchg_success = eq.to_scalar().to_bool()? + && if can_fail_spuriously { + this.machine.rng.get_mut().gen_bool(success_rate) + } else { + true + }; + let res = Immediate::ScalarPair(old.to_scalar(), Scalar::from_bool(cmpxchg_success)); + + // Update ptr depending on comparison. + // if successful, perform a full rw-atomic validation + // otherwise treat this as an atomic load with the fail ordering. + if cmpxchg_success { + this.allow_data_races_mut(|this| this.write_scalar(new, &place.into()))?; + this.validate_atomic_rmw(place, success)?; + this.buffered_atomic_rmw(new, place, success, old.to_scalar())?; + } else { + this.validate_atomic_load(place, fail)?; + // A failed compare exchange is equivalent to a load, reading from the latest store + // in the modification order. + // Since `old` is only a value and not the store element, we need to separately + // find it in our store buffer and perform load_impl on it. + this.perform_read_on_buffered_latest(place, fail, old.to_scalar())?; + } + + // Return the old value. + Ok(res) + } + + /// Update the data-race detector for an atomic fence on the current thread. + fn atomic_fence(&mut self, atomic: AtomicFenceOrd) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + if let Some(data_race) = &mut this.machine.data_race { + data_race.maybe_perform_sync_operation(&this.machine.threads, |index, mut clocks| { + log::trace!("Atomic fence on {:?} with ordering {:?}", index, atomic); + + // Apply data-race detection for the current fences + // this treats AcqRel and SeqCst as the same as an acquire + // and release fence applied in the same timestamp. + if atomic != AtomicFenceOrd::Release { + // Either Acquire | AcqRel | SeqCst + clocks.apply_acquire_fence(); + } + if atomic != AtomicFenceOrd::Acquire { + // Either Release | AcqRel | SeqCst + clocks.apply_release_fence(); + } + if atomic == AtomicFenceOrd::SeqCst { + data_race.last_sc_fence.borrow_mut().set_at_index(&clocks.clock, index); + clocks.fence_seqcst.join(&data_race.last_sc_fence.borrow()); + clocks.write_seqcst.join(&data_race.last_sc_write.borrow()); + } + + // Increment timestamp in case of release semantics. + Ok(atomic != AtomicFenceOrd::Acquire) + }) + } else { + Ok(()) + } + } + + /// After all threads are done running, this allows data races to occur for subsequent + /// 'administrative' machine accesses (that logically happen outside of the Abstract Machine). + fn allow_data_races_all_threads_done(&mut self) { + let this = self.eval_context_ref(); + assert!(this.have_all_terminated()); + if let Some(data_race) = &this.machine.data_race { + let old = data_race.ongoing_action_data_race_free.replace(true); + assert!(!old, "cannot nest allow_data_races"); + } + } +} + +/// Vector clock metadata for a logical memory allocation. +#[derive(Debug, Clone)] +pub struct VClockAlloc { + /// Assigning each byte a MemoryCellClocks. + alloc_ranges: RefCell>, +} + +impl VClockAlloc { + /// Create a new data-race detector for newly allocated memory. + pub fn new_allocation( + global: &GlobalState, + thread_mgr: &ThreadManager<'_, '_>, + len: Size, + kind: MemoryKind, + ) -> VClockAlloc { + let (alloc_timestamp, alloc_index) = match kind { + // User allocated and stack memory should track allocation. + MemoryKind::Machine( + MiriMemoryKind::Rust | MiriMemoryKind::C | MiriMemoryKind::WinHeap, + ) + | MemoryKind::Stack => { + let (alloc_index, clocks) = global.current_thread_state(thread_mgr); + let alloc_timestamp = clocks.clock[alloc_index]; + (alloc_timestamp, alloc_index) + } + // Other global memory should trace races but be allocated at the 0 timestamp. + MemoryKind::Machine( + MiriMemoryKind::Global + | MiriMemoryKind::Machine + | MiriMemoryKind::Runtime + | MiriMemoryKind::ExternStatic + | MiriMemoryKind::Tls, + ) + | MemoryKind::CallerLocation => (0, VectorIdx::MAX_INDEX), + }; + VClockAlloc { + alloc_ranges: RefCell::new(RangeMap::new( + len, + MemoryCellClocks::new(alloc_timestamp, alloc_index), + )), + } + } + + // Find an index, if one exists where the value + // in `l` is greater than the value in `r`. + fn find_gt_index(l: &VClock, r: &VClock) -> Option { + log::trace!("Find index where not {:?} <= {:?}", l, r); + let l_slice = l.as_slice(); + let r_slice = r.as_slice(); + l_slice + .iter() + .zip(r_slice.iter()) + .enumerate() + .find_map(|(idx, (&l, &r))| if l > r { Some(idx) } else { None }) + .or_else(|| { + if l_slice.len() > r_slice.len() { + // By invariant, if l_slice is longer + // then one element must be larger. + // This just validates that this is true + // and reports earlier elements first. + let l_remainder_slice = &l_slice[r_slice.len()..]; + let idx = l_remainder_slice + .iter() + .enumerate() + .find_map(|(idx, &r)| if r == 0 { None } else { Some(idx) }) + .expect("Invalid VClock Invariant"); + Some(idx + r_slice.len()) + } else { + None + } + }) + .map(VectorIdx::new) + } + + /// Report a data-race found in the program. + /// This finds the two racing threads and the type + /// of data-race that occurred. This will also + /// return info about the memory location the data-race + /// occurred in. + #[cold] + #[inline(never)] + fn report_data_race<'tcx>( + global: &GlobalState, + thread_mgr: &ThreadManager<'_, '_>, + range: &MemoryCellClocks, + action: &str, + is_atomic: bool, + ptr_dbg: Pointer, + ) -> InterpResult<'tcx> { + let (current_index, current_clocks) = global.current_thread_state(thread_mgr); + let write_clock; + let (other_action, other_thread, _other_clock) = if range.write + > current_clocks.clock[range.write_index] + { + // Convert the write action into the vector clock it + // represents for diagnostic purposes. + write_clock = VClock::new_with_index(range.write_index, range.write); + (range.write_type.get_descriptor(), range.write_index, &write_clock) + } else if let Some(idx) = Self::find_gt_index(&range.read, ¤t_clocks.clock) { + ("Read", idx, &range.read) + } else if !is_atomic { + if let Some(atomic) = range.atomic() { + if let Some(idx) = Self::find_gt_index(&atomic.write_vector, ¤t_clocks.clock) + { + ("Atomic Store", idx, &atomic.write_vector) + } else if let Some(idx) = + Self::find_gt_index(&atomic.read_vector, ¤t_clocks.clock) + { + ("Atomic Load", idx, &atomic.read_vector) + } else { + unreachable!( + "Failed to report data-race for non-atomic operation: no race found" + ) + } + } else { + unreachable!( + "Failed to report data-race for non-atomic operation: no atomic component" + ) + } + } else { + unreachable!("Failed to report data-race for atomic operation") + }; + + // Load elaborated thread information about the racing thread actions. + let current_thread_info = global.print_thread_metadata(thread_mgr, current_index); + let other_thread_info = global.print_thread_metadata(thread_mgr, other_thread); + + // Throw the data-race detection. + throw_ub_format!( + "Data race detected between {} on {} and {} on {} at {:?}", + action, + current_thread_info, + other_action, + other_thread_info, + ptr_dbg, + ) + } + + /// Detect racing atomic read and writes (not data races) + /// on every byte of the current access range + pub(super) fn race_free_with_atomic( + &self, + range: AllocRange, + global: &GlobalState, + thread_mgr: &ThreadManager<'_, '_>, + ) -> bool { + if global.race_detecting() { + let (_, clocks) = global.current_thread_state(thread_mgr); + let alloc_ranges = self.alloc_ranges.borrow(); + for (_, range) in alloc_ranges.iter(range.start, range.size) { + if !range.race_free_with_atomic(&clocks) { + return false; + } + } + } + true + } + + /// Detect data-races for an unsynchronized read operation, will not perform + /// data-race detection if `race_detecting()` is false, either due to no threads + /// being created or if it is temporarily disabled during a racy read or write + /// operation for which data-race detection is handled separately, for example + /// atomic read operations. + pub fn read<'tcx>( + &self, + alloc_id: AllocId, + range: AllocRange, + global: &GlobalState, + thread_mgr: &ThreadManager<'_, '_>, + ) -> InterpResult<'tcx> { + if global.race_detecting() { + let (index, clocks) = global.current_thread_state(thread_mgr); + let mut alloc_ranges = self.alloc_ranges.borrow_mut(); + for (offset, range) in alloc_ranges.iter_mut(range.start, range.size) { + if let Err(DataRace) = range.read_race_detect(&clocks, index) { + // Report data-race. + return Self::report_data_race( + global, + thread_mgr, + range, + "Read", + false, + Pointer::new(alloc_id, offset), + ); + } + } + Ok(()) + } else { + Ok(()) + } + } + + // Shared code for detecting data-races on unique access to a section of memory + fn unique_access<'tcx>( + &mut self, + alloc_id: AllocId, + range: AllocRange, + write_type: WriteType, + global: &mut GlobalState, + thread_mgr: &ThreadManager<'_, '_>, + ) -> InterpResult<'tcx> { + if global.race_detecting() { + let (index, clocks) = global.current_thread_state(thread_mgr); + for (offset, range) in self.alloc_ranges.get_mut().iter_mut(range.start, range.size) { + if let Err(DataRace) = range.write_race_detect(&clocks, index, write_type) { + // Report data-race + return Self::report_data_race( + global, + thread_mgr, + range, + write_type.get_descriptor(), + false, + Pointer::new(alloc_id, offset), + ); + } + } + Ok(()) + } else { + Ok(()) + } + } + + /// Detect data-races for an unsynchronized write operation, will not perform + /// data-race threads if `race_detecting()` is false, either due to no threads + /// being created or if it is temporarily disabled during a racy read or write + /// operation + pub fn write<'tcx>( + &mut self, + alloc_id: AllocId, + range: AllocRange, + global: &mut GlobalState, + thread_mgr: &ThreadManager<'_, '_>, + ) -> InterpResult<'tcx> { + self.unique_access(alloc_id, range, WriteType::Write, global, thread_mgr) + } + + /// Detect data-races for an unsynchronized deallocate operation, will not perform + /// data-race threads if `race_detecting()` is false, either due to no threads + /// being created or if it is temporarily disabled during a racy read or write + /// operation + pub fn deallocate<'tcx>( + &mut self, + alloc_id: AllocId, + range: AllocRange, + global: &mut GlobalState, + thread_mgr: &ThreadManager<'_, '_>, + ) -> InterpResult<'tcx> { + self.unique_access(alloc_id, range, WriteType::Deallocate, global, thread_mgr) + } +} + +impl<'mir, 'tcx: 'mir> EvalContextPrivExt<'mir, 'tcx> for MiriInterpCx<'mir, 'tcx> {} +trait EvalContextPrivExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> { + /// Temporarily allow data-races to occur. This should only be used in + /// one of these cases: + /// - One of the appropriate `validate_atomic` functions will be called to + /// to treat a memory access as atomic. + /// - The memory being accessed should be treated as internal state, that + /// cannot be accessed by the interpreted program. + /// - Execution of the interpreted program execution has halted. + #[inline] + fn allow_data_races_ref(&self, op: impl FnOnce(&MiriInterpCx<'mir, 'tcx>) -> R) -> R { + let this = self.eval_context_ref(); + if let Some(data_race) = &this.machine.data_race { + let old = data_race.ongoing_action_data_race_free.replace(true); + assert!(!old, "cannot nest allow_data_races"); + } + let result = op(this); + if let Some(data_race) = &this.machine.data_race { + data_race.ongoing_action_data_race_free.set(false); + } + result + } + + /// Same as `allow_data_races_ref`, this temporarily disables any data-race detection and + /// so should only be used for atomic operations or internal state that the program cannot + /// access. + #[inline] + fn allow_data_races_mut( + &mut self, + op: impl FnOnce(&mut MiriInterpCx<'mir, 'tcx>) -> R, + ) -> R { + let this = self.eval_context_mut(); + if let Some(data_race) = &this.machine.data_race { + let old = data_race.ongoing_action_data_race_free.replace(true); + assert!(!old, "cannot nest allow_data_races"); + } + let result = op(this); + if let Some(data_race) = &this.machine.data_race { + data_race.ongoing_action_data_race_free.set(false); + } + result + } + + /// Checks that an atomic access is legal at the given place. + fn atomic_access_check(&self, place: &MPlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> { + let this = self.eval_context_ref(); + // Check alignment requirements. Atomics must always be aligned to their size, + // even if the type they wrap would be less aligned (e.g. AtomicU64 on 32bit must + // be 8-aligned). + let align = Align::from_bytes(place.layout.size.bytes()).unwrap(); + this.check_ptr_access_align( + place.ptr, + place.layout.size, + align, + CheckInAllocMsg::MemoryAccessTest, + )?; + // Ensure the allocation is mutable. Even failing (read-only) compare_exchange need mutable + // memory on many targets (i.e., they segfault if taht memory is mapped read-only), and + // atomic loads can be implemented via compare_exchange on some targets. There could + // possibly be some very specific exceptions to this, see + // for details. + // We avoid `get_ptr_alloc` since we do *not* want to run the access hooks -- the actual + // access will happen later. + let (alloc_id, _offset, _prov) = + this.ptr_try_get_alloc_id(place.ptr).expect("there are no zero-sized atomic accesses"); + if this.get_alloc_mutability(alloc_id)? == Mutability::Not { + // FIXME: make this prettier, once these messages have separate title/span/help messages. + throw_ub_format!( + "atomic operations cannot be performed on read-only memory\n\ + many platforms require atomic read-modify-write instructions to be performed on writeable memory, even if the operation fails \ + (and is hence nominally read-only)\n\ + some platforms implement (some) atomic loads via compare-exchange, which means they do not work on read-only memory; \ + it is possible that we could have an exception permitting this for specific kinds of loads\n\ + please report an issue at if this is a problem for you" + ); + } + Ok(()) + } + + /// Update the data-race detector for an atomic read occurring at the + /// associated memory-place and on the current thread. + fn validate_atomic_load( + &self, + place: &MPlaceTy<'tcx, Provenance>, + atomic: AtomicReadOrd, + ) -> InterpResult<'tcx> { + let this = self.eval_context_ref(); + this.validate_overlapping_atomic(place)?; + this.validate_atomic_op( + place, + atomic, + "Atomic Load", + move |memory, clocks, index, atomic| { + if atomic == AtomicReadOrd::Relaxed { + memory.load_relaxed(&mut *clocks, index) + } else { + memory.load_acquire(&mut *clocks, index) + } + }, + ) + } + + /// Update the data-race detector for an atomic write occurring at the + /// associated memory-place and on the current thread. + fn validate_atomic_store( + &mut self, + place: &MPlaceTy<'tcx, Provenance>, + atomic: AtomicWriteOrd, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + this.validate_overlapping_atomic(place)?; + this.validate_atomic_op( + place, + atomic, + "Atomic Store", + move |memory, clocks, index, atomic| { + if atomic == AtomicWriteOrd::Relaxed { + memory.store_relaxed(clocks, index) + } else { + memory.store_release(clocks, index) + } + }, + ) + } + + /// Update the data-race detector for an atomic read-modify-write occurring + /// at the associated memory place and on the current thread. + fn validate_atomic_rmw( + &mut self, + place: &MPlaceTy<'tcx, Provenance>, + atomic: AtomicRwOrd, + ) -> InterpResult<'tcx> { + use AtomicRwOrd::*; + let acquire = matches!(atomic, Acquire | AcqRel | SeqCst); + let release = matches!(atomic, Release | AcqRel | SeqCst); + let this = self.eval_context_mut(); + this.validate_overlapping_atomic(place)?; + this.validate_atomic_op(place, atomic, "Atomic RMW", move |memory, clocks, index, _| { + if acquire { + memory.load_acquire(clocks, index)?; + } else { + memory.load_relaxed(clocks, index)?; + } + if release { + memory.rmw_release(clocks, index) + } else { + memory.rmw_relaxed(clocks, index) + } + }) + } + + /// Generic atomic operation implementation + fn validate_atomic_op( + &self, + place: &MPlaceTy<'tcx, Provenance>, + atomic: A, + description: &str, + mut op: impl FnMut( + &mut MemoryCellClocks, + &mut ThreadClockSet, + VectorIdx, + A, + ) -> Result<(), DataRace>, + ) -> InterpResult<'tcx> { + let this = self.eval_context_ref(); + if let Some(data_race) = &this.machine.data_race { + if data_race.race_detecting() { + let size = place.layout.size; + let (alloc_id, base_offset, _prov) = this.ptr_get_alloc_id(place.ptr)?; + // Load and log the atomic operation. + // Note that atomic loads are possible even from read-only allocations, so `get_alloc_extra_mut` is not an option. + let alloc_meta = this.get_alloc_extra(alloc_id)?.data_race.as_ref().unwrap(); + log::trace!( + "Atomic op({}) with ordering {:?} on {:?} (size={})", + description, + &atomic, + place.ptr, + size.bytes() + ); + + // Perform the atomic operation. + data_race.maybe_perform_sync_operation( + &this.machine.threads, + |index, mut clocks| { + for (offset, range) in + alloc_meta.alloc_ranges.borrow_mut().iter_mut(base_offset, size) + { + if let Err(DataRace) = op(range, &mut clocks, index, atomic) { + mem::drop(clocks); + return VClockAlloc::report_data_race( + data_race, + &this.machine.threads, + range, + description, + true, + Pointer::new(alloc_id, offset), + ) + .map(|_| true); + } + } + + // This conservatively assumes all operations have release semantics + Ok(true) + }, + )?; + + // Log changes to atomic memory. + if log::log_enabled!(log::Level::Trace) { + for (_offset, range) in alloc_meta.alloc_ranges.borrow().iter(base_offset, size) + { + log::trace!( + "Updated atomic memory({:?}, size={}) to {:#?}", + place.ptr, + size.bytes(), + range.atomic_ops + ); + } + } + } + } + Ok(()) + } +} + +/// Extra metadata associated with a thread. +#[derive(Debug, Clone, Default)] +struct ThreadExtraState { + /// The current vector index in use by the + /// thread currently, this is set to None + /// after the vector index has been re-used + /// and hence the value will never need to be + /// read during data-race reporting. + vector_index: Option, + + /// Thread termination vector clock, this + /// is set on thread termination and is used + /// for joining on threads since the vector_index + /// may be re-used when the join operation occurs. + termination_vector_clock: Option, +} + +/// Global data-race detection state, contains the currently +/// executing thread as well as the vector-clocks associated +/// with each of the threads. +// FIXME: it is probably better to have one large RefCell, than to have so many small ones. +#[derive(Debug, Clone)] +pub struct GlobalState { + /// Set to true once the first additional + /// thread has launched, due to the dependency + /// between before and after a thread launch. + /// Any data-races must be recorded after this + /// so concurrent execution can ignore recording + /// any data-races. + multi_threaded: Cell, + + /// A flag to mark we are currently performing + /// a data race free action (such as atomic access) + /// to supress the race detector + ongoing_action_data_race_free: Cell, + + /// Mapping of a vector index to a known set of thread + /// clocks, this is not directly mapping from a thread id + /// since it may refer to multiple threads. + vector_clocks: RefCell>, + + /// Mapping of a given vector index to the current thread + /// that the execution is representing, this may change + /// if a vector index is re-assigned to a new thread. + vector_info: RefCell>, + + /// The mapping of a given thread to associated thread metadata. + thread_info: RefCell>, + + /// Potential vector indices that could be re-used on thread creation + /// values are inserted here on after the thread has terminated and + /// been joined with, and hence may potentially become free + /// for use as the index for a new thread. + /// Elements in this set may still require the vector index to + /// report data-races, and can only be re-used after all + /// active vector-clocks catch up with the threads timestamp. + reuse_candidates: RefCell>, + + /// This contains threads that have terminated, but not yet joined + /// and so cannot become re-use candidates until a join operation + /// occurs. + /// The associated vector index will be moved into re-use candidates + /// after the join operation occurs. + terminated_threads: RefCell>, + + /// The timestamp of last SC fence performed by each thread + last_sc_fence: RefCell, + + /// The timestamp of last SC write performed by each thread + last_sc_write: RefCell, + + /// Track when an outdated (weak memory) load happens. + pub track_outdated_loads: bool, +} + +impl GlobalState { + /// Create a new global state, setup with just thread-id=0 + /// advanced to timestamp = 1. + pub fn new(config: &MiriConfig) -> Self { + let mut global_state = GlobalState { + multi_threaded: Cell::new(false), + ongoing_action_data_race_free: Cell::new(false), + vector_clocks: RefCell::new(IndexVec::new()), + vector_info: RefCell::new(IndexVec::new()), + thread_info: RefCell::new(IndexVec::new()), + reuse_candidates: RefCell::new(FxHashSet::default()), + terminated_threads: RefCell::new(FxHashMap::default()), + last_sc_fence: RefCell::new(VClock::default()), + last_sc_write: RefCell::new(VClock::default()), + track_outdated_loads: config.track_outdated_loads, + }; + + // Setup the main-thread since it is not explicitly created: + // uses vector index and thread-id 0. + let index = global_state.vector_clocks.get_mut().push(ThreadClockSet::default()); + global_state.vector_info.get_mut().push(ThreadId::new(0)); + global_state + .thread_info + .get_mut() + .push(ThreadExtraState { vector_index: Some(index), termination_vector_clock: None }); + + global_state + } + + // We perform data race detection when there are more than 1 active thread + // and we have not temporarily disabled race detection to perform something + // data race free + fn race_detecting(&self) -> bool { + self.multi_threaded.get() && !self.ongoing_action_data_race_free.get() + } + + pub fn ongoing_action_data_race_free(&self) -> bool { + self.ongoing_action_data_race_free.get() + } + + // Try to find vector index values that can potentially be re-used + // by a new thread instead of a new vector index being created. + fn find_vector_index_reuse_candidate(&self) -> Option { + let mut reuse = self.reuse_candidates.borrow_mut(); + let vector_clocks = self.vector_clocks.borrow(); + let vector_info = self.vector_info.borrow(); + let terminated_threads = self.terminated_threads.borrow(); + for &candidate in reuse.iter() { + let target_timestamp = vector_clocks[candidate].clock[candidate]; + if vector_clocks.iter_enumerated().all(|(clock_idx, clock)| { + // The thread happens before the clock, and hence cannot report + // a data-race with this the candidate index. + let no_data_race = clock.clock[candidate] >= target_timestamp; + + // The vector represents a thread that has terminated and hence cannot + // report a data-race with the candidate index. + let thread_id = vector_info[clock_idx]; + let vector_terminated = + reuse.contains(&clock_idx) || terminated_threads.contains_key(&thread_id); + + // The vector index cannot report a race with the candidate index + // and hence allows the candidate index to be re-used. + no_data_race || vector_terminated + }) { + // All vector clocks for each vector index are equal to + // the target timestamp, and the thread is known to have + // terminated, therefore this vector clock index cannot + // report any more data-races. + assert!(reuse.remove(&candidate)); + return Some(candidate); + } + } + None + } + + // Hook for thread creation, enabled multi-threaded execution and marks + // the current thread timestamp as happening-before the current thread. + #[inline] + pub fn thread_created(&mut self, thread_mgr: &ThreadManager<'_, '_>, thread: ThreadId) { + let current_index = self.current_index(thread_mgr); + + // Enable multi-threaded execution, there are now at least two threads + // so data-races are now possible. + self.multi_threaded.set(true); + + // Load and setup the associated thread metadata + let mut thread_info = self.thread_info.borrow_mut(); + thread_info.ensure_contains_elem(thread, Default::default); + + // Assign a vector index for the thread, attempting to re-use an old + // vector index that can no longer report any data-races if possible. + let created_index = if let Some(reuse_index) = self.find_vector_index_reuse_candidate() { + // Now re-configure the re-use candidate, increment the clock + // for the new sync use of the vector. + let vector_clocks = self.vector_clocks.get_mut(); + vector_clocks[reuse_index].increment_clock(reuse_index); + + // Locate the old thread the vector was associated with and update + // it to represent the new thread instead. + let vector_info = self.vector_info.get_mut(); + let old_thread = vector_info[reuse_index]; + vector_info[reuse_index] = thread; + + // Mark the thread the vector index was associated with as no longer + // representing a thread index. + thread_info[old_thread].vector_index = None; + + reuse_index + } else { + // No vector re-use candidates available, instead create + // a new vector index. + let vector_info = self.vector_info.get_mut(); + vector_info.push(thread) + }; + + log::trace!("Creating thread = {:?} with vector index = {:?}", thread, created_index); + + // Mark the chosen vector index as in use by the thread. + thread_info[thread].vector_index = Some(created_index); + + // Create a thread clock set if applicable. + let vector_clocks = self.vector_clocks.get_mut(); + if created_index == vector_clocks.next_index() { + vector_clocks.push(ThreadClockSet::default()); + } + + // Now load the two clocks and configure the initial state. + let (current, created) = vector_clocks.pick2_mut(current_index, created_index); + + // Join the created with current, since the current threads + // previous actions happen-before the created thread. + created.join_with(current); + + // Advance both threads after the synchronized operation. + // Both operations are considered to have release semantics. + current.increment_clock(current_index); + created.increment_clock(created_index); + } + + /// Hook on a thread join to update the implicit happens-before relation between the joined + /// thread (the joinee, the thread that someone waited on) and the current thread (the joiner, + /// the thread who was waiting). + #[inline] + pub fn thread_joined( + &mut self, + thread_mgr: &ThreadManager<'_, '_>, + joiner: ThreadId, + joinee: ThreadId, + ) { + let clocks_vec = self.vector_clocks.get_mut(); + let thread_info = self.thread_info.get_mut(); + + // Load the vector clock of the current thread. + let current_index = thread_info[joiner] + .vector_index + .expect("Performed thread join on thread with no assigned vector"); + let current = &mut clocks_vec[current_index]; + + // Load the associated vector clock for the terminated thread. + let join_clock = thread_info[joinee] + .termination_vector_clock + .as_ref() + .expect("Joined with thread but thread has not terminated"); + + // The join thread happens-before the current thread + // so update the current vector clock. + // Is not a release operation so the clock is not incremented. + current.clock.join(join_clock); + + // Check the number of live threads, if the value is 1 + // then test for potentially disabling multi-threaded execution. + if thread_mgr.get_live_thread_count() == 1 { + // May potentially be able to disable multi-threaded execution. + let current_clock = &clocks_vec[current_index]; + if clocks_vec + .iter_enumerated() + .all(|(idx, clocks)| clocks.clock[idx] <= current_clock.clock[idx]) + { + // All thread terminations happen-before the current clock + // therefore no data-races can be reported until a new thread + // is created, so disable multi-threaded execution. + self.multi_threaded.set(false); + } + } + + // If the thread is marked as terminated but not joined + // then move the thread to the re-use set. + let termination = self.terminated_threads.get_mut(); + if let Some(index) = termination.remove(&joinee) { + let reuse = self.reuse_candidates.get_mut(); + reuse.insert(index); + } + } + + /// On thread termination, the vector-clock may re-used + /// in the future once all remaining thread-clocks catch + /// up with the time index of the terminated thread. + /// This assigns thread termination with a unique index + /// which will be used to join the thread + /// This should be called strictly before any calls to + /// `thread_joined`. + #[inline] + pub fn thread_terminated(&mut self, thread_mgr: &ThreadManager<'_, '_>) { + let current_index = self.current_index(thread_mgr); + + // Increment the clock to a unique termination timestamp. + let vector_clocks = self.vector_clocks.get_mut(); + let current_clocks = &mut vector_clocks[current_index]; + current_clocks.increment_clock(current_index); + + // Load the current thread id for the executing vector. + let vector_info = self.vector_info.get_mut(); + let current_thread = vector_info[current_index]; + + // Load the current thread metadata, and move to a terminated + // vector state. Setting up the vector clock all join operations + // will use. + let thread_info = self.thread_info.get_mut(); + let current = &mut thread_info[current_thread]; + current.termination_vector_clock = Some(current_clocks.clock.clone()); + + // Add this thread as a candidate for re-use after a thread join + // occurs. + let termination = self.terminated_threads.get_mut(); + termination.insert(current_thread, current_index); + } + + /// Attempt to perform a synchronized operation, this + /// will perform no operation if multi-threading is + /// not currently enabled. + /// Otherwise it will increment the clock for the current + /// vector before and after the operation for data-race + /// detection between any happens-before edges the + /// operation may create. + fn maybe_perform_sync_operation<'tcx>( + &self, + thread_mgr: &ThreadManager<'_, '_>, + op: impl FnOnce(VectorIdx, RefMut<'_, ThreadClockSet>) -> InterpResult<'tcx, bool>, + ) -> InterpResult<'tcx> { + if self.multi_threaded.get() { + let (index, clocks) = self.current_thread_state_mut(thread_mgr); + if op(index, clocks)? { + let (_, mut clocks) = self.current_thread_state_mut(thread_mgr); + clocks.increment_clock(index); + } + } + Ok(()) + } + + /// Internal utility to identify a thread stored internally + /// returns the id and the name for better diagnostics. + fn print_thread_metadata( + &self, + thread_mgr: &ThreadManager<'_, '_>, + vector: VectorIdx, + ) -> String { + let thread = self.vector_info.borrow()[vector]; + let thread_name = thread_mgr.get_thread_name(thread); + format!("thread `{}`", String::from_utf8_lossy(thread_name)) + } + + /// Acquire a lock, express that the previous call of + /// `validate_lock_release` must happen before this. + /// As this is an acquire operation, the thread timestamp is not + /// incremented. + pub fn validate_lock_acquire(&self, lock: &VClock, thread: ThreadId) { + let (_, mut clocks) = self.load_thread_state_mut(thread); + clocks.clock.join(lock); + } + + /// Release a lock handle, express that this happens-before + /// any subsequent calls to `validate_lock_acquire`. + /// For normal locks this should be equivalent to `validate_lock_release_shared` + /// since an acquire operation should have occurred before, however + /// for futex & condvar operations this is not the case and this + /// operation must be used. + pub fn validate_lock_release(&self, lock: &mut VClock, thread: ThreadId) { + let (index, mut clocks) = self.load_thread_state_mut(thread); + lock.clone_from(&clocks.clock); + clocks.increment_clock(index); + } + + /// Release a lock handle, express that this happens-before + /// any subsequent calls to `validate_lock_acquire` as well + /// as any previous calls to this function after any + /// `validate_lock_release` calls. + /// For normal locks this should be equivalent to `validate_lock_release`. + /// This function only exists for joining over the set of concurrent readers + /// in a read-write lock and should not be used for anything else. + pub fn validate_lock_release_shared(&self, lock: &mut VClock, thread: ThreadId) { + let (index, mut clocks) = self.load_thread_state_mut(thread); + lock.join(&clocks.clock); + clocks.increment_clock(index); + } + + /// Load the vector index used by the given thread as well as the set of vector clocks + /// used by the thread. + #[inline] + fn load_thread_state_mut(&self, thread: ThreadId) -> (VectorIdx, RefMut<'_, ThreadClockSet>) { + let index = self.thread_info.borrow()[thread] + .vector_index + .expect("Loading thread state for thread with no assigned vector"); + let ref_vector = self.vector_clocks.borrow_mut(); + let clocks = RefMut::map(ref_vector, |vec| &mut vec[index]); + (index, clocks) + } + + /// Load the current vector clock in use and the current set of thread clocks + /// in use for the vector. + #[inline] + pub(super) fn current_thread_state( + &self, + thread_mgr: &ThreadManager<'_, '_>, + ) -> (VectorIdx, Ref<'_, ThreadClockSet>) { + let index = self.current_index(thread_mgr); + let ref_vector = self.vector_clocks.borrow(); + let clocks = Ref::map(ref_vector, |vec| &vec[index]); + (index, clocks) + } + + /// Load the current vector clock in use and the current set of thread clocks + /// in use for the vector mutably for modification. + #[inline] + pub(super) fn current_thread_state_mut( + &self, + thread_mgr: &ThreadManager<'_, '_>, + ) -> (VectorIdx, RefMut<'_, ThreadClockSet>) { + let index = self.current_index(thread_mgr); + let ref_vector = self.vector_clocks.borrow_mut(); + let clocks = RefMut::map(ref_vector, |vec| &mut vec[index]); + (index, clocks) + } + + /// Return the current thread, should be the same + /// as the data-race active thread. + #[inline] + fn current_index(&self, thread_mgr: &ThreadManager<'_, '_>) -> VectorIdx { + let active_thread_id = thread_mgr.get_active_thread_id(); + self.thread_info.borrow()[active_thread_id] + .vector_index + .expect("active thread has no assigned vector") + } + + // SC ATOMIC STORE rule in the paper. + pub(super) fn sc_write(&self, thread_mgr: &ThreadManager<'_, '_>) { + let (index, clocks) = self.current_thread_state(thread_mgr); + self.last_sc_write.borrow_mut().set_at_index(&clocks.clock, index); + } + + // SC ATOMIC READ rule in the paper. + pub(super) fn sc_read(&self, thread_mgr: &ThreadManager<'_, '_>) { + let (.., mut clocks) = self.current_thread_state_mut(thread_mgr); + clocks.read_seqcst.join(&self.last_sc_fence.borrow()); + } +} diff --git a/src/tools/miri/src/concurrency/mod.rs b/src/tools/miri/src/concurrency/mod.rs new file mode 100644 index 0000000000000..61ef3d5640e00 --- /dev/null +++ b/src/tools/miri/src/concurrency/mod.rs @@ -0,0 +1,6 @@ +pub mod data_race; +mod range_object_map; +pub mod sync; +pub mod thread; +mod vector_clock; +pub mod weak_memory; diff --git a/src/tools/miri/src/concurrency/range_object_map.rs b/src/tools/miri/src/concurrency/range_object_map.rs new file mode 100644 index 0000000000000..50d3f8c9b20ea --- /dev/null +++ b/src/tools/miri/src/concurrency/range_object_map.rs @@ -0,0 +1,277 @@ +//! Implements a map from allocation ranges to data. This is somewhat similar to RangeMap, but the +//! ranges and data are discrete and non-splittable -- they represent distinct "objects". An +//! allocation in the map will always have the same range until explicitly removed + +use rustc_target::abi::Size; +use std::ops::{Index, IndexMut, Range}; + +use rustc_const_eval::interpret::AllocRange; + +#[derive(Clone, Debug)] +struct Elem { + /// The range covered by this element; never empty. + range: AllocRange, + /// The data stored for this element. + data: T, +} + +/// Index of an allocation within the map +type Position = usize; + +#[derive(Clone, Debug)] +pub struct RangeObjectMap { + v: Vec>, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum AccessType { + /// The access perfectly overlaps (same offset and range) with the exsiting allocation + PerfectlyOverlapping(Position), + /// The access does not touch any exising allocation + Empty(Position), + /// The access overlaps with one or more existing allocations + ImperfectlyOverlapping(Range), +} + +impl RangeObjectMap { + pub fn new() -> Self { + Self { v: Vec::new() } + } + + /// Finds the position of the allocation containing the given offset. If the offset is not + /// in an existing allocation, then returns Err containing the position + /// where such allocation should be inserted + fn find_offset(&self, offset: Size) -> Result { + // We do a binary search. + let mut left = 0usize; // inclusive + let mut right = self.v.len(); // exclusive + loop { + if left == right { + // No element contains the given offset. But the + // position is where such element should be placed at. + return Err(left); + } + let candidate = left.checked_add(right).unwrap() / 2; + let elem = &self.v[candidate]; + if offset < elem.range.start { + // We are too far right (offset is further left). + debug_assert!(candidate < right); // we are making progress + right = candidate; + } else if offset >= elem.range.end() { + // We are too far left (offset is further right). + debug_assert!(candidate >= left); // we are making progress + left = candidate + 1; + } else { + // This is it! + return Ok(candidate); + } + } + } + + /// Determines whether a given access on `range` overlaps with + /// an existing allocation + pub fn access_type(&self, range: AllocRange) -> AccessType { + match self.find_offset(range.start) { + Ok(pos) => { + // Start of the range belongs to an existing object, now let's check the overlapping situation + let elem = &self.v[pos]; + // FIXME: derive Eq for AllocRange in rustc + if elem.range.start == range.start && elem.range.size == range.size { + // Happy case: perfectly overlapping access + AccessType::PerfectlyOverlapping(pos) + } else { + // FIXME: add a last() method to AllocRange that returns the last inclusive offset (end() is exclusive) + let end_pos = match self.find_offset(range.end() - Size::from_bytes(1)) { + // If the end lands in an existing object, add one to get the exclusive position + Ok(inclusive_pos) => inclusive_pos + 1, + Err(exclusive_pos) => exclusive_pos, + }; + + AccessType::ImperfectlyOverlapping(pos..end_pos) + } + } + Err(pos) => { + // Start of the range doesn't belong to an existing object + match self.find_offset(range.end() - Size::from_bytes(1)) { + // Neither does the end + Err(end_pos) => + if pos == end_pos { + // There's nothing between the start and the end, so the range thing is empty + AccessType::Empty(pos) + } else { + // Otherwise we have entirely covered an existing object + AccessType::ImperfectlyOverlapping(pos..end_pos) + }, + // Otherwise at least part of it overlaps with something else + Ok(end_pos) => AccessType::ImperfectlyOverlapping(pos..end_pos + 1), + } + } + } + } + + /// Inserts an object and its occupied range at given position + // The Position can be calculated from AllocRange, but the only user of AllocationMap + // always calls access_type before calling insert/index/index_mut, and we don't + // want to repeat the binary search on each time, so we ask the caller to supply Position + pub fn insert_at_pos(&mut self, pos: Position, range: AllocRange, data: T) { + self.v.insert(pos, Elem { range, data }); + // If we aren't the first element, then our start must be greater than the preivous element's end + if pos > 0 { + assert!(self.v[pos - 1].range.end() <= range.start); + } + // If we aren't the last element, then our end must be smaller than next element's start + if pos < self.v.len() - 1 { + assert!(range.end() <= self.v[pos + 1].range.start); + } + } + + pub fn remove_pos_range(&mut self, pos_range: Range) { + self.v.drain(pos_range); + } + + pub fn remove_from_pos(&mut self, pos: Position) { + self.v.remove(pos); + } +} + +impl Index for RangeObjectMap { + type Output = T; + + fn index(&self, pos: Position) -> &Self::Output { + &self.v[pos].data + } +} + +impl IndexMut for RangeObjectMap { + fn index_mut(&mut self, pos: Position) -> &mut Self::Output { + &mut self.v[pos].data + } +} + +#[cfg(test)] +mod tests { + use rustc_const_eval::interpret::alloc_range; + + use super::*; + + #[test] + fn empty_map() { + // FIXME: make Size::from_bytes const + let four = Size::from_bytes(4); + let map = RangeObjectMap::<()>::new(); + + // Correctly tells where we should insert the first element (at position 0) + assert_eq!(map.find_offset(Size::from_bytes(3)), Err(0)); + + // Correctly tells the access type along with the supposed position + assert_eq!(map.access_type(alloc_range(Size::ZERO, four)), AccessType::Empty(0)); + } + + #[test] + #[should_panic] + fn no_overlapping_inserts() { + let four = Size::from_bytes(4); + + let mut map = RangeObjectMap::<&str>::new(); + + // |_|_|_|_|#|#|#|#|_|_|_|_|... + // 0 1 2 3 4 5 6 7 8 9 a b c d + map.insert_at_pos(0, alloc_range(four, four), "#"); + // |_|_|_|_|#|#|#|#|_|_|_|_|... + // 0 ^ ^ ^ ^ 5 6 7 8 9 a b c d + map.insert_at_pos(0, alloc_range(Size::from_bytes(1), four), "@"); + } + + #[test] + fn boundaries() { + let four = Size::from_bytes(4); + + let mut map = RangeObjectMap::<&str>::new(); + + // |#|#|#|#|_|_|... + // 0 1 2 3 4 5 + map.insert_at_pos(0, alloc_range(Size::ZERO, four), "#"); + // |#|#|#|#|_|_|... + // 0 1 2 3 ^ 5 + assert_eq!(map.find_offset(four), Err(1)); + // |#|#|#|#|_|_|_|_|_|... + // 0 1 2 3 ^ ^ ^ ^ 8 + assert_eq!(map.access_type(alloc_range(four, four)), AccessType::Empty(1)); + + let eight = Size::from_bytes(8); + // |#|#|#|#|_|_|_|_|@|@|@|@|_|_|... + // 0 1 2 3 4 5 6 7 8 9 a b c d + map.insert_at_pos(1, alloc_range(eight, four), "@"); + // |#|#|#|#|_|_|_|_|@|@|@|@|_|_|... + // 0 1 2 3 4 5 6 ^ 8 9 a b c d + assert_eq!(map.find_offset(Size::from_bytes(7)), Err(1)); + // |#|#|#|#|_|_|_|_|@|@|@|@|_|_|... + // 0 1 2 3 ^ ^ ^ ^ 8 9 a b c d + assert_eq!(map.access_type(alloc_range(four, four)), AccessType::Empty(1)); + } + + #[test] + fn perfectly_overlapping() { + let four = Size::from_bytes(4); + + let mut map = RangeObjectMap::<&str>::new(); + + // |#|#|#|#|_|_|... + // 0 1 2 3 4 5 + map.insert_at_pos(0, alloc_range(Size::ZERO, four), "#"); + // |#|#|#|#|_|_|... + // ^ ^ ^ ^ 4 5 + assert_eq!(map.find_offset(Size::ZERO), Ok(0)); + assert_eq!( + map.access_type(alloc_range(Size::ZERO, four)), + AccessType::PerfectlyOverlapping(0) + ); + + // |#|#|#|#|@|@|@|@|_|... + // 0 1 2 3 4 5 6 7 8 + map.insert_at_pos(1, alloc_range(four, four), "@"); + // |#|#|#|#|@|@|@|@|_|... + // 0 1 2 3 ^ ^ ^ ^ 8 + assert_eq!(map.find_offset(four), Ok(1)); + assert_eq!(map.access_type(alloc_range(four, four)), AccessType::PerfectlyOverlapping(1)); + } + + #[test] + fn straddling() { + let four = Size::from_bytes(4); + + let mut map = RangeObjectMap::<&str>::new(); + + // |_|_|_|_|#|#|#|#|_|_|_|_|... + // 0 1 2 3 4 5 6 7 8 9 a b c d + map.insert_at_pos(0, alloc_range(four, four), "#"); + // |_|_|_|_|#|#|#|#|_|_|_|_|... + // 0 1 ^ ^ ^ ^ 6 7 8 9 a b c d + assert_eq!( + map.access_type(alloc_range(Size::from_bytes(2), four)), + AccessType::ImperfectlyOverlapping(0..1) + ); + // |_|_|_|_|#|#|#|#|_|_|_|_|... + // 0 1 2 3 4 5 ^ ^ ^ ^ a b c d + assert_eq!( + map.access_type(alloc_range(Size::from_bytes(6), four)), + AccessType::ImperfectlyOverlapping(0..1) + ); + // |_|_|_|_|#|#|#|#|_|_|_|_|... + // 0 1 ^ ^ ^ ^ ^ ^ ^ ^ a b c d + assert_eq!( + map.access_type(alloc_range(Size::from_bytes(2), Size::from_bytes(8))), + AccessType::ImperfectlyOverlapping(0..1) + ); + + // |_|_|_|_|#|#|#|#|_|_|@|@|_|_|... + // 0 1 2 3 4 5 6 7 8 9 a b c d + map.insert_at_pos(1, alloc_range(Size::from_bytes(10), Size::from_bytes(2)), "@"); + // |_|_|_|_|#|#|#|#|_|_|@|@|_|_|... + // 0 1 2 3 4 5 ^ ^ ^ ^ ^ ^ ^ ^ + assert_eq!( + map.access_type(alloc_range(Size::from_bytes(6), Size::from_bytes(8))), + AccessType::ImperfectlyOverlapping(0..2) + ); + } +} diff --git a/src/tools/miri/src/concurrency/sync.rs b/src/tools/miri/src/concurrency/sync.rs new file mode 100644 index 0000000000000..464f452ca7696 --- /dev/null +++ b/src/tools/miri/src/concurrency/sync.rs @@ -0,0 +1,584 @@ +use std::collections::{hash_map::Entry, VecDeque}; +use std::num::NonZeroU32; +use std::ops::Not; + +use log::trace; + +use rustc_data_structures::fx::FxHashMap; +use rustc_index::vec::{Idx, IndexVec}; + +use super::vector_clock::VClock; +use crate::*; + +/// We cannot use the `newtype_index!` macro because we have to use 0 as a +/// sentinel value meaning that the identifier is not assigned. This is because +/// the pthreads static initializers initialize memory with zeros (see the +/// `src/shims/sync.rs` file). +macro_rules! declare_id { + ($name: ident) => { + /// 0 is used to indicate that the id was not yet assigned and, + /// therefore, is not a valid identifier. + #[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)] + pub struct $name(NonZeroU32); + + impl $name { + // Panics if `id == 0`. + pub fn from_u32(id: u32) -> Self { + Self(NonZeroU32::new(id).unwrap()) + } + } + + impl Idx for $name { + fn new(idx: usize) -> Self { + // We use 0 as a sentinel value (see the comment above) and, + // therefore, need to shift by one when converting from an index + // into a vector. + let shifted_idx = u32::try_from(idx).unwrap().checked_add(1).unwrap(); + $name(NonZeroU32::new(shifted_idx).unwrap()) + } + fn index(self) -> usize { + // See the comment in `Self::new`. + // (This cannot underflow because self is NonZeroU32.) + usize::try_from(self.0.get() - 1).unwrap() + } + } + + impl $name { + pub fn to_u32_scalar(&self) -> Scalar { + Scalar::from_u32(self.0.get()) + } + } + }; +} + +declare_id!(MutexId); + +/// The mutex state. +#[derive(Default, Debug)] +struct Mutex { + /// The thread that currently owns the lock. + owner: Option, + /// How many times the mutex was locked by the owner. + lock_count: usize, + /// The queue of threads waiting for this mutex. + queue: VecDeque, + /// Data race handle, this tracks the happens-before + /// relationship between each mutex access. It is + /// released to during unlock and acquired from during + /// locking, and therefore stores the clock of the last + /// thread to release this mutex. + data_race: VClock, +} + +declare_id!(RwLockId); + +/// The read-write lock state. +#[derive(Default, Debug)] +struct RwLock { + /// The writer thread that currently owns the lock. + writer: Option, + /// The readers that currently own the lock and how many times they acquired + /// the lock. + readers: FxHashMap, + /// The queue of writer threads waiting for this lock. + writer_queue: VecDeque, + /// The queue of reader threads waiting for this lock. + reader_queue: VecDeque, + /// Data race handle for writers, tracks the happens-before + /// ordering between each write access to a rwlock and is updated + /// after a sequence of concurrent readers to track the happens- + /// before ordering between the set of previous readers and + /// the current writer. + /// Contains the clock of the last thread to release a writer + /// lock or the joined clock of the set of last threads to release + /// shared reader locks. + data_race: VClock, + /// Data race handle for readers, this is temporary storage + /// for the combined happens-before ordering for between all + /// concurrent readers and the next writer, and the value + /// is stored to the main data_race variable once all + /// readers are finished. + /// Has to be stored separately since reader lock acquires + /// must load the clock of the last write and must not + /// add happens-before orderings between shared reader + /// locks. + data_race_reader: VClock, +} + +declare_id!(CondvarId); + +/// A thread waiting on a conditional variable. +#[derive(Debug)] +struct CondvarWaiter { + /// The thread that is waiting on this variable. + thread: ThreadId, + /// The mutex on which the thread is waiting. + mutex: MutexId, +} + +/// The conditional variable state. +#[derive(Default, Debug)] +struct Condvar { + waiters: VecDeque, + /// Tracks the happens-before relationship + /// between a cond-var signal and a cond-var + /// wait during a non-suprious signal event. + /// Contains the clock of the last thread to + /// perform a futex-signal. + data_race: VClock, +} + +/// The futex state. +#[derive(Default, Debug)] +struct Futex { + waiters: VecDeque, + /// Tracks the happens-before relationship + /// between a futex-wake and a futex-wait + /// during a non-spurious wake event. + /// Contains the clock of the last thread to + /// perform a futex-wake. + data_race: VClock, +} + +/// A thread waiting on a futex. +#[derive(Debug)] +struct FutexWaiter { + /// The thread that is waiting on this futex. + thread: ThreadId, + /// The bitset used by FUTEX_*_BITSET, or u32::MAX for other operations. + bitset: u32, +} + +/// The state of all synchronization variables. +#[derive(Default, Debug)] +pub(crate) struct SynchronizationState { + mutexes: IndexVec, + rwlocks: IndexVec, + condvars: IndexVec, + futexes: FxHashMap, +} + +// Private extension trait for local helper methods +impl<'mir, 'tcx: 'mir> EvalContextExtPriv<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + /// Take a reader out of the queue waiting for the lock. + /// Returns `true` if some thread got the rwlock. + #[inline] + fn rwlock_dequeue_and_lock_reader(&mut self, id: RwLockId) -> bool { + let this = self.eval_context_mut(); + if let Some(reader) = this.machine.threads.sync.rwlocks[id].reader_queue.pop_front() { + this.unblock_thread(reader); + this.rwlock_reader_lock(id, reader); + true + } else { + false + } + } + + /// Take the writer out of the queue waiting for the lock. + /// Returns `true` if some thread got the rwlock. + #[inline] + fn rwlock_dequeue_and_lock_writer(&mut self, id: RwLockId) -> bool { + let this = self.eval_context_mut(); + if let Some(writer) = this.machine.threads.sync.rwlocks[id].writer_queue.pop_front() { + this.unblock_thread(writer); + this.rwlock_writer_lock(id, writer); + true + } else { + false + } + } + + /// Take a thread out of the queue waiting for the mutex, and lock + /// the mutex for it. Returns `true` if some thread has the mutex now. + #[inline] + fn mutex_dequeue_and_lock(&mut self, id: MutexId) -> bool { + let this = self.eval_context_mut(); + if let Some(thread) = this.machine.threads.sync.mutexes[id].queue.pop_front() { + this.unblock_thread(thread); + this.mutex_lock(id, thread); + true + } else { + false + } + } +} + +// Public interface to synchronization primitives. Please note that in most +// cases, the function calls are infallible and it is the client's (shim +// implementation's) responsibility to detect and deal with erroneous +// situations. +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + #[inline] + /// Create state for a new mutex. + fn mutex_create(&mut self) -> MutexId { + let this = self.eval_context_mut(); + this.machine.threads.sync.mutexes.push(Default::default()) + } + + #[inline] + /// Provides the closure with the next MutexId. Creates that mutex if the closure returns None, + /// otherwise returns the value from the closure + fn mutex_get_or_create(&mut self, existing: F) -> InterpResult<'tcx, MutexId> + where + F: FnOnce(&mut MiriInterpCx<'mir, 'tcx>, MutexId) -> InterpResult<'tcx, Option>, + { + let this = self.eval_context_mut(); + let next_index = this.machine.threads.sync.mutexes.next_index(); + if let Some(old) = existing(this, next_index)? { + Ok(old) + } else { + let new_index = this.machine.threads.sync.mutexes.push(Default::default()); + assert_eq!(next_index, new_index); + Ok(new_index) + } + } + + #[inline] + /// Get the id of the thread that currently owns this lock. + fn mutex_get_owner(&mut self, id: MutexId) -> ThreadId { + let this = self.eval_context_ref(); + this.machine.threads.sync.mutexes[id].owner.unwrap() + } + + #[inline] + /// Check if locked. + fn mutex_is_locked(&self, id: MutexId) -> bool { + let this = self.eval_context_ref(); + this.machine.threads.sync.mutexes[id].owner.is_some() + } + + /// Lock by setting the mutex owner and increasing the lock count. + fn mutex_lock(&mut self, id: MutexId, thread: ThreadId) { + let this = self.eval_context_mut(); + let mutex = &mut this.machine.threads.sync.mutexes[id]; + if let Some(current_owner) = mutex.owner { + assert_eq!(thread, current_owner, "mutex already locked by another thread"); + assert!( + mutex.lock_count > 0, + "invariant violation: lock_count == 0 iff the thread is unlocked" + ); + } else { + mutex.owner = Some(thread); + } + mutex.lock_count = mutex.lock_count.checked_add(1).unwrap(); + if let Some(data_race) = &this.machine.data_race { + data_race.validate_lock_acquire(&mutex.data_race, thread); + } + } + + /// Try unlocking by decreasing the lock count and returning the old lock + /// count. If the lock count reaches 0, release the lock and potentially + /// give to a new owner. If the lock was not locked by `expected_owner`, + /// return `None`. + fn mutex_unlock(&mut self, id: MutexId, expected_owner: ThreadId) -> Option { + let this = self.eval_context_mut(); + let mutex = &mut this.machine.threads.sync.mutexes[id]; + if let Some(current_owner) = mutex.owner { + // Mutex is locked. + if current_owner != expected_owner { + // Only the owner can unlock the mutex. + return None; + } + let old_lock_count = mutex.lock_count; + mutex.lock_count = old_lock_count + .checked_sub(1) + .expect("invariant violation: lock_count == 0 iff the thread is unlocked"); + if mutex.lock_count == 0 { + mutex.owner = None; + // The mutex is completely unlocked. Try transfering ownership + // to another thread. + if let Some(data_race) = &this.machine.data_race { + data_race.validate_lock_release(&mut mutex.data_race, current_owner); + } + this.mutex_dequeue_and_lock(id); + } + Some(old_lock_count) + } else { + // Mutex is not locked. + None + } + } + + #[inline] + /// Put the thread into the queue waiting for the mutex. + fn mutex_enqueue_and_block(&mut self, id: MutexId, thread: ThreadId) { + let this = self.eval_context_mut(); + assert!(this.mutex_is_locked(id), "queing on unlocked mutex"); + this.machine.threads.sync.mutexes[id].queue.push_back(thread); + this.block_thread(thread); + } + + #[inline] + /// Create state for a new read write lock. + fn rwlock_create(&mut self) -> RwLockId { + let this = self.eval_context_mut(); + this.machine.threads.sync.rwlocks.push(Default::default()) + } + + #[inline] + /// Provides the closure with the next RwLockId. Creates that RwLock if the closure returns None, + /// otherwise returns the value from the closure + fn rwlock_get_or_create(&mut self, existing: F) -> InterpResult<'tcx, RwLockId> + where + F: FnOnce(&mut MiriInterpCx<'mir, 'tcx>, RwLockId) -> InterpResult<'tcx, Option>, + { + let this = self.eval_context_mut(); + let next_index = this.machine.threads.sync.rwlocks.next_index(); + if let Some(old) = existing(this, next_index)? { + Ok(old) + } else { + let new_index = this.machine.threads.sync.rwlocks.push(Default::default()); + assert_eq!(next_index, new_index); + Ok(new_index) + } + } + + #[inline] + /// Check if locked. + fn rwlock_is_locked(&self, id: RwLockId) -> bool { + let this = self.eval_context_ref(); + let rwlock = &this.machine.threads.sync.rwlocks[id]; + trace!( + "rwlock_is_locked: {:?} writer is {:?} and there are {} reader threads (some of which could hold multiple read locks)", + id, + rwlock.writer, + rwlock.readers.len(), + ); + rwlock.writer.is_some() || rwlock.readers.is_empty().not() + } + + #[inline] + /// Check if write locked. + fn rwlock_is_write_locked(&self, id: RwLockId) -> bool { + let this = self.eval_context_ref(); + let rwlock = &this.machine.threads.sync.rwlocks[id]; + trace!("rwlock_is_write_locked: {:?} writer is {:?}", id, rwlock.writer); + rwlock.writer.is_some() + } + + /// Read-lock the lock by adding the `reader` the list of threads that own + /// this lock. + fn rwlock_reader_lock(&mut self, id: RwLockId, reader: ThreadId) { + let this = self.eval_context_mut(); + assert!(!this.rwlock_is_write_locked(id), "the lock is write locked"); + trace!("rwlock_reader_lock: {:?} now also held (one more time) by {:?}", id, reader); + let rwlock = &mut this.machine.threads.sync.rwlocks[id]; + let count = rwlock.readers.entry(reader).or_insert(0); + *count = count.checked_add(1).expect("the reader counter overflowed"); + if let Some(data_race) = &this.machine.data_race { + data_race.validate_lock_acquire(&rwlock.data_race, reader); + } + } + + /// Try read-unlock the lock for `reader` and potentially give the lock to a new owner. + /// Returns `true` if succeeded, `false` if this `reader` did not hold the lock. + fn rwlock_reader_unlock(&mut self, id: RwLockId, reader: ThreadId) -> bool { + let this = self.eval_context_mut(); + let rwlock = &mut this.machine.threads.sync.rwlocks[id]; + match rwlock.readers.entry(reader) { + Entry::Occupied(mut entry) => { + let count = entry.get_mut(); + assert!(*count > 0, "rwlock locked with count == 0"); + *count -= 1; + if *count == 0 { + trace!("rwlock_reader_unlock: {:?} no longer held by {:?}", id, reader); + entry.remove(); + } else { + trace!("rwlock_reader_unlock: {:?} held one less time by {:?}", id, reader); + } + } + Entry::Vacant(_) => return false, // we did not even own this lock + } + if let Some(data_race) = &this.machine.data_race { + data_race.validate_lock_release_shared(&mut rwlock.data_race_reader, reader); + } + + // The thread was a reader. If the lock is not held any more, give it to a writer. + if this.rwlock_is_locked(id).not() { + // All the readers are finished, so set the writer data-race handle to the value + // of the union of all reader data race handles, since the set of readers + // happen-before the writers + let rwlock = &mut this.machine.threads.sync.rwlocks[id]; + rwlock.data_race.clone_from(&rwlock.data_race_reader); + this.rwlock_dequeue_and_lock_writer(id); + } + true + } + + #[inline] + /// Put the reader in the queue waiting for the lock and block it. + fn rwlock_enqueue_and_block_reader(&mut self, id: RwLockId, reader: ThreadId) { + let this = self.eval_context_mut(); + assert!(this.rwlock_is_write_locked(id), "read-queueing on not write locked rwlock"); + this.machine.threads.sync.rwlocks[id].reader_queue.push_back(reader); + this.block_thread(reader); + } + + #[inline] + /// Lock by setting the writer that owns the lock. + fn rwlock_writer_lock(&mut self, id: RwLockId, writer: ThreadId) { + let this = self.eval_context_mut(); + assert!(!this.rwlock_is_locked(id), "the rwlock is already locked"); + trace!("rwlock_writer_lock: {:?} now held by {:?}", id, writer); + let rwlock = &mut this.machine.threads.sync.rwlocks[id]; + rwlock.writer = Some(writer); + if let Some(data_race) = &this.machine.data_race { + data_race.validate_lock_acquire(&rwlock.data_race, writer); + } + } + + #[inline] + /// Try to unlock by removing the writer. + fn rwlock_writer_unlock(&mut self, id: RwLockId, expected_writer: ThreadId) -> bool { + let this = self.eval_context_mut(); + let rwlock = &mut this.machine.threads.sync.rwlocks[id]; + if let Some(current_writer) = rwlock.writer { + if current_writer != expected_writer { + // Only the owner can unlock the rwlock. + return false; + } + rwlock.writer = None; + trace!("rwlock_writer_unlock: {:?} unlocked by {:?}", id, expected_writer); + // Release memory to both reader and writer vector clocks + // since this writer happens-before both the union of readers once they are finished + // and the next writer + if let Some(data_race) = &this.machine.data_race { + data_race.validate_lock_release(&mut rwlock.data_race, current_writer); + data_race.validate_lock_release(&mut rwlock.data_race_reader, current_writer); + } + // The thread was a writer. + // + // We are prioritizing writers here against the readers. As a + // result, not only readers can starve writers, but also writers can + // starve readers. + if this.rwlock_dequeue_and_lock_writer(id) { + // Someone got the write lock, nice. + } else { + // Give the lock to all readers. + while this.rwlock_dequeue_and_lock_reader(id) { + // Rinse and repeat. + } + } + true + } else { + false + } + } + + #[inline] + /// Put the writer in the queue waiting for the lock. + fn rwlock_enqueue_and_block_writer(&mut self, id: RwLockId, writer: ThreadId) { + let this = self.eval_context_mut(); + assert!(this.rwlock_is_locked(id), "write-queueing on unlocked rwlock"); + this.machine.threads.sync.rwlocks[id].writer_queue.push_back(writer); + this.block_thread(writer); + } + + #[inline] + /// Create state for a new conditional variable. + fn condvar_create(&mut self) -> CondvarId { + let this = self.eval_context_mut(); + this.machine.threads.sync.condvars.push(Default::default()) + } + + #[inline] + /// Provides the closure with the next CondvarId. Creates that Condvar if the closure returns None, + /// otherwise returns the value from the closure + fn condvar_get_or_create(&mut self, existing: F) -> InterpResult<'tcx, CondvarId> + where + F: FnOnce( + &mut MiriInterpCx<'mir, 'tcx>, + CondvarId, + ) -> InterpResult<'tcx, Option>, + { + let this = self.eval_context_mut(); + let next_index = this.machine.threads.sync.condvars.next_index(); + if let Some(old) = existing(this, next_index)? { + Ok(old) + } else { + let new_index = this.machine.threads.sync.condvars.push(Default::default()); + assert_eq!(next_index, new_index); + Ok(new_index) + } + } + + #[inline] + /// Is the conditional variable awaited? + fn condvar_is_awaited(&mut self, id: CondvarId) -> bool { + let this = self.eval_context_mut(); + !this.machine.threads.sync.condvars[id].waiters.is_empty() + } + + /// Mark that the thread is waiting on the conditional variable. + fn condvar_wait(&mut self, id: CondvarId, thread: ThreadId, mutex: MutexId) { + let this = self.eval_context_mut(); + let waiters = &mut this.machine.threads.sync.condvars[id].waiters; + assert!(waiters.iter().all(|waiter| waiter.thread != thread), "thread is already waiting"); + waiters.push_back(CondvarWaiter { thread, mutex }); + } + + /// Wake up some thread (if there is any) sleeping on the conditional + /// variable. + fn condvar_signal(&mut self, id: CondvarId) -> Option<(ThreadId, MutexId)> { + let this = self.eval_context_mut(); + let current_thread = this.get_active_thread(); + let condvar = &mut this.machine.threads.sync.condvars[id]; + let data_race = &this.machine.data_race; + + // Each condvar signal happens-before the end of the condvar wake + if let Some(data_race) = data_race { + data_race.validate_lock_release(&mut condvar.data_race, current_thread); + } + condvar.waiters.pop_front().map(|waiter| { + if let Some(data_race) = data_race { + data_race.validate_lock_acquire(&condvar.data_race, waiter.thread); + } + (waiter.thread, waiter.mutex) + }) + } + + #[inline] + /// Remove the thread from the queue of threads waiting on this conditional variable. + fn condvar_remove_waiter(&mut self, id: CondvarId, thread: ThreadId) { + let this = self.eval_context_mut(); + this.machine.threads.sync.condvars[id].waiters.retain(|waiter| waiter.thread != thread); + } + + fn futex_wait(&mut self, addr: u64, thread: ThreadId, bitset: u32) { + let this = self.eval_context_mut(); + let futex = &mut this.machine.threads.sync.futexes.entry(addr).or_default(); + let waiters = &mut futex.waiters; + assert!(waiters.iter().all(|waiter| waiter.thread != thread), "thread is already waiting"); + waiters.push_back(FutexWaiter { thread, bitset }); + } + + fn futex_wake(&mut self, addr: u64, bitset: u32) -> Option { + let this = self.eval_context_mut(); + let current_thread = this.get_active_thread(); + let futex = &mut this.machine.threads.sync.futexes.get_mut(&addr)?; + let data_race = &this.machine.data_race; + + // Each futex-wake happens-before the end of the futex wait + if let Some(data_race) = data_race { + data_race.validate_lock_release(&mut futex.data_race, current_thread); + } + + // Wake up the first thread in the queue that matches any of the bits in the bitset. + futex.waiters.iter().position(|w| w.bitset & bitset != 0).map(|i| { + let waiter = futex.waiters.remove(i).unwrap(); + if let Some(data_race) = data_race { + data_race.validate_lock_acquire(&futex.data_race, waiter.thread); + } + waiter.thread + }) + } + + fn futex_remove_waiter(&mut self, addr: u64, thread: ThreadId) { + let this = self.eval_context_mut(); + if let Some(futex) = this.machine.threads.sync.futexes.get_mut(&addr) { + futex.waiters.retain(|waiter| waiter.thread != thread); + } + } +} diff --git a/src/tools/miri/src/concurrency/thread.rs b/src/tools/miri/src/concurrency/thread.rs new file mode 100644 index 0000000000000..f1a3d19fb4cbc --- /dev/null +++ b/src/tools/miri/src/concurrency/thread.rs @@ -0,0 +1,933 @@ +//! Implements threads. + +use std::cell::RefCell; +use std::collections::hash_map::Entry; +use std::num::TryFromIntError; +use std::time::{Duration, SystemTime}; + +use log::trace; + +use rustc_data_structures::fx::FxHashMap; +use rustc_hir::def_id::DefId; +use rustc_index::vec::{Idx, IndexVec}; +use rustc_middle::mir::Mutability; +use rustc_middle::ty::layout::TyAndLayout; +use rustc_target::spec::abi::Abi; + +use crate::concurrency::data_race; +use crate::concurrency::sync::SynchronizationState; +use crate::*; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum SchedulingAction { + /// Execute step on the active thread. + ExecuteStep, + /// Execute a timeout callback. + ExecuteTimeoutCallback, + /// Execute destructors of the active thread. + ExecuteDtors, + /// Stop the program. + Stop, +} + +/// Timeout callbacks can be created by synchronization primitives to tell the +/// scheduler that they should be called once some period of time passes. +type TimeoutCallback<'mir, 'tcx> = Box< + dyn FnOnce(&mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>) -> InterpResult<'tcx> + 'tcx, +>; + +/// A thread identifier. +#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)] +pub struct ThreadId(u32); + +/// The main thread. When it terminates, the whole application terminates. +const MAIN_THREAD: ThreadId = ThreadId(0); + +impl ThreadId { + pub fn to_u32(self) -> u32 { + self.0 + } +} + +impl Idx for ThreadId { + fn new(idx: usize) -> Self { + ThreadId(u32::try_from(idx).unwrap()) + } + + fn index(self) -> usize { + usize::try_from(self.0).unwrap() + } +} + +impl TryFrom for ThreadId { + type Error = TryFromIntError; + fn try_from(id: u64) -> Result { + u32::try_from(id).map(Self) + } +} + +impl From for ThreadId { + fn from(id: u32) -> Self { + Self(id) + } +} + +impl From for u64 { + fn from(t: ThreadId) -> Self { + t.0.into() + } +} + +/// The state of a thread. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum ThreadState { + /// The thread is enabled and can be executed. + Enabled, + /// The thread tried to join the specified thread and is blocked until that + /// thread terminates. + BlockedOnJoin(ThreadId), + /// The thread is blocked on some synchronization primitive. It is the + /// responsibility of the synchronization primitives to track threads that + /// are blocked by them. + BlockedOnSync, + /// The thread has terminated its execution. We do not delete terminated + /// threads (FIXME: why?). + Terminated, +} + +/// The join status of a thread. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +enum ThreadJoinStatus { + /// The thread can be joined. + Joinable, + /// A thread is detached if its join handle was destroyed and no other + /// thread can join it. + Detached, + /// The thread was already joined by some thread and cannot be joined again. + Joined, +} + +/// A thread. +pub struct Thread<'mir, 'tcx> { + state: ThreadState, + + /// Name of the thread. + thread_name: Option>, + + /// The virtual call stack. + stack: Vec>>, + + /// The join status. + join_status: ThreadJoinStatus, + + /// The temporary used for storing the argument of + /// the call to `miri_start_panic` (the panic payload) when unwinding. + /// This is pointer-sized, and matches the `Payload` type in `src/libpanic_unwind/miri.rs`. + pub(crate) panic_payload: Option>, + + /// Last OS error location in memory. It is a 32-bit integer. + pub(crate) last_error: Option>, +} + +impl<'mir, 'tcx> Thread<'mir, 'tcx> { + /// Check if the thread is done executing (no more stack frames). If yes, + /// change the state to terminated and return `true`. + fn check_terminated(&mut self) -> bool { + if self.state == ThreadState::Enabled { + if self.stack.is_empty() { + self.state = ThreadState::Terminated; + return true; + } + } + false + } + + /// Get the name of the current thread, or `` if it was not set. + fn thread_name(&self) -> &[u8] { + if let Some(ref thread_name) = self.thread_name { thread_name } else { b"" } + } +} + +impl<'mir, 'tcx> std::fmt::Debug for Thread<'mir, 'tcx> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}({:?}, {:?})", + String::from_utf8_lossy(self.thread_name()), + self.state, + self.join_status + ) + } +} + +impl<'mir, 'tcx> Default for Thread<'mir, 'tcx> { + fn default() -> Self { + Self { + state: ThreadState::Enabled, + thread_name: None, + stack: Vec::new(), + join_status: ThreadJoinStatus::Joinable, + panic_payload: None, + last_error: None, + } + } +} + +impl<'mir, 'tcx> Thread<'mir, 'tcx> { + fn new(name: &str) -> Self { + let mut thread = Thread::default(); + thread.thread_name = Some(Vec::from(name.as_bytes())); + thread + } +} + +/// A specific moment in time. +#[derive(Debug)] +pub enum Time { + Monotonic(Instant), + RealTime(SystemTime), +} + +impl Time { + /// How long do we have to wait from now until the specified time? + fn get_wait_time(&self, clock: &Clock) -> Duration { + match self { + Time::Monotonic(instant) => instant.duration_since(clock.now()), + Time::RealTime(time) => + time.duration_since(SystemTime::now()).unwrap_or(Duration::new(0, 0)), + } + } +} + +/// Callbacks are used to implement timeouts. For example, waiting on a +/// conditional variable with a timeout creates a callback that is called after +/// the specified time and unblocks the thread. If another thread signals on the +/// conditional variable, the signal handler deletes the callback. +struct TimeoutCallbackInfo<'mir, 'tcx> { + /// The callback should be called no earlier than this time. + call_time: Time, + /// The called function. + callback: TimeoutCallback<'mir, 'tcx>, +} + +impl<'mir, 'tcx> std::fmt::Debug for TimeoutCallbackInfo<'mir, 'tcx> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "TimeoutCallback({:?})", self.call_time) + } +} + +/// A set of threads. +#[derive(Debug)] +pub struct ThreadManager<'mir, 'tcx> { + /// Identifier of the currently active thread. + active_thread: ThreadId, + /// Threads used in the program. + /// + /// Note that this vector also contains terminated threads. + threads: IndexVec>, + /// This field is pub(crate) because the synchronization primitives + /// (`crate::sync`) need a way to access it. + pub(crate) sync: SynchronizationState, + /// A mapping from a thread-local static to an allocation id of a thread + /// specific allocation. + thread_local_alloc_ids: RefCell>>, + /// A flag that indicates that we should change the active thread. + yield_active_thread: bool, + /// Callbacks that are called once the specified time passes. + timeout_callbacks: FxHashMap>, +} + +impl<'mir, 'tcx> Default for ThreadManager<'mir, 'tcx> { + fn default() -> Self { + let mut threads = IndexVec::new(); + // Create the main thread and add it to the list of threads. + threads.push(Thread::new("main")); + Self { + active_thread: ThreadId::new(0), + threads, + sync: SynchronizationState::default(), + thread_local_alloc_ids: Default::default(), + yield_active_thread: false, + timeout_callbacks: FxHashMap::default(), + } + } +} + +impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> { + pub(crate) fn init(ecx: &mut MiriInterpCx<'mir, 'tcx>) { + if ecx.tcx.sess.target.os.as_ref() != "windows" { + // The main thread can *not* be joined on except on windows. + ecx.machine.threads.threads[ThreadId::new(0)].join_status = ThreadJoinStatus::Detached; + } + } + + /// Check if we have an allocation for the given thread local static for the + /// active thread. + fn get_thread_local_alloc_id(&self, def_id: DefId) -> Option> { + self.thread_local_alloc_ids.borrow().get(&(def_id, self.active_thread)).cloned() + } + + /// Set the pointer for the allocation of the given thread local + /// static for the active thread. + /// + /// Panics if a thread local is initialized twice for the same thread. + fn set_thread_local_alloc(&self, def_id: DefId, ptr: Pointer) { + self.thread_local_alloc_ids + .borrow_mut() + .try_insert((def_id, self.active_thread), ptr) + .unwrap(); + } + + /// Borrow the stack of the active thread. + pub fn active_thread_stack(&self) -> &[Frame<'mir, 'tcx, Provenance, FrameData<'tcx>>] { + &self.threads[self.active_thread].stack + } + + /// Mutably borrow the stack of the active thread. + fn active_thread_stack_mut( + &mut self, + ) -> &mut Vec>> { + &mut self.threads[self.active_thread].stack + } + + pub fn iter(&self) -> impl Iterator> { + self.threads.iter() + } + + pub fn all_stacks( + &self, + ) -> impl Iterator>]> { + self.threads.iter().map(|t| &t.stack[..]) + } + + /// Create a new thread and returns its id. + fn create_thread(&mut self) -> ThreadId { + let new_thread_id = ThreadId::new(self.threads.len()); + self.threads.push(Default::default()); + new_thread_id + } + + /// Set an active thread and return the id of the thread that was active before. + fn set_active_thread_id(&mut self, id: ThreadId) -> ThreadId { + let active_thread_id = self.active_thread; + self.active_thread = id; + assert!(self.active_thread.index() < self.threads.len()); + active_thread_id + } + + /// Get the id of the currently active thread. + pub fn get_active_thread_id(&self) -> ThreadId { + self.active_thread + } + + /// Get the total number of threads that were ever spawn by this program. + pub fn get_total_thread_count(&self) -> usize { + self.threads.len() + } + + /// Get the total of threads that are currently live, i.e., not yet terminated. + /// (They might be blocked.) + pub fn get_live_thread_count(&self) -> usize { + self.threads.iter().filter(|t| !matches!(t.state, ThreadState::Terminated)).count() + } + + /// Has the given thread terminated? + fn has_terminated(&self, thread_id: ThreadId) -> bool { + self.threads[thread_id].state == ThreadState::Terminated + } + + /// Have all threads terminated? + fn have_all_terminated(&self) -> bool { + self.threads.iter().all(|thread| thread.state == ThreadState::Terminated) + } + + /// Enable the thread for execution. The thread must be terminated. + fn enable_thread(&mut self, thread_id: ThreadId) { + assert!(self.has_terminated(thread_id)); + self.threads[thread_id].state = ThreadState::Enabled; + } + + /// Get a mutable borrow of the currently active thread. + fn active_thread_mut(&mut self) -> &mut Thread<'mir, 'tcx> { + &mut self.threads[self.active_thread] + } + + /// Get a shared borrow of the currently active thread. + fn active_thread_ref(&self) -> &Thread<'mir, 'tcx> { + &self.threads[self.active_thread] + } + + /// Mark the thread as detached, which means that no other thread will try + /// to join it and the thread is responsible for cleaning up. + /// + /// `allow_terminated_joined` allows detaching joined threads that have already terminated. + /// This matches Windows's behavior for `CloseHandle`. + /// + /// See : + /// > The handle is valid until closed, even after the thread it represents has been terminated. + fn detach_thread(&mut self, id: ThreadId, allow_terminated_joined: bool) -> InterpResult<'tcx> { + trace!("detaching {:?}", id); + + let is_ub = if allow_terminated_joined && self.threads[id].state == ThreadState::Terminated + { + // "Detached" in particular means "not yet joined". Redundant detaching is still UB. + self.threads[id].join_status == ThreadJoinStatus::Detached + } else { + self.threads[id].join_status != ThreadJoinStatus::Joinable + }; + if is_ub { + throw_ub_format!("trying to detach thread that was already detached or joined"); + } + + self.threads[id].join_status = ThreadJoinStatus::Detached; + Ok(()) + } + + /// Mark that the active thread tries to join the thread with `joined_thread_id`. + fn join_thread( + &mut self, + joined_thread_id: ThreadId, + data_race: Option<&mut data_race::GlobalState>, + ) -> InterpResult<'tcx> { + if self.threads[joined_thread_id].join_status == ThreadJoinStatus::Detached { + throw_ub_format!("trying to join a detached thread"); + } + + // Mark the joined thread as being joined so that we detect if other + // threads try to join it. + self.threads[joined_thread_id].join_status = ThreadJoinStatus::Joined; + if self.threads[joined_thread_id].state != ThreadState::Terminated { + // The joined thread is still running, we need to wait for it. + self.active_thread_mut().state = ThreadState::BlockedOnJoin(joined_thread_id); + trace!( + "{:?} blocked on {:?} when trying to join", + self.active_thread, + joined_thread_id + ); + } else { + // The thread has already terminated - mark join happens-before + if let Some(data_race) = data_race { + data_race.thread_joined(self, self.active_thread, joined_thread_id); + } + } + Ok(()) + } + + /// Mark that the active thread tries to exclusively join the thread with `joined_thread_id`. + /// If the thread is already joined by another thread, it will throw UB + fn join_thread_exclusive( + &mut self, + joined_thread_id: ThreadId, + data_race: Option<&mut data_race::GlobalState>, + ) -> InterpResult<'tcx> { + if self.threads[joined_thread_id].join_status == ThreadJoinStatus::Joined { + throw_ub_format!("trying to join an already joined thread"); + } + + if joined_thread_id == self.active_thread { + throw_ub_format!("trying to join itself"); + } + + assert!( + self.threads + .iter() + .all(|thread| thread.state != ThreadState::BlockedOnJoin(joined_thread_id)), + "this thread already has threads waiting for its termination" + ); + + self.join_thread(joined_thread_id, data_race) + } + + /// Set the name of the given thread. + pub fn set_thread_name(&mut self, thread: ThreadId, new_thread_name: Vec) { + self.threads[thread].thread_name = Some(new_thread_name); + } + + /// Get the name of the given thread. + pub fn get_thread_name(&self, thread: ThreadId) -> &[u8] { + self.threads[thread].thread_name() + } + + /// Put the thread into the blocked state. + fn block_thread(&mut self, thread: ThreadId) { + let state = &mut self.threads[thread].state; + assert_eq!(*state, ThreadState::Enabled); + *state = ThreadState::BlockedOnSync; + } + + /// Put the blocked thread into the enabled state. + fn unblock_thread(&mut self, thread: ThreadId) { + let state = &mut self.threads[thread].state; + assert_eq!(*state, ThreadState::BlockedOnSync); + *state = ThreadState::Enabled; + } + + /// Change the active thread to some enabled thread. + fn yield_active_thread(&mut self) { + // We do not yield immediately, as swapping out the current stack while executing a MIR statement + // could lead to all sorts of confusion. + // We should only switch stacks between steps. + self.yield_active_thread = true; + } + + /// Register the given `callback` to be called once the `call_time` passes. + /// + /// The callback will be called with `thread` being the active thread, and + /// the callback may not change the active thread. + fn register_timeout_callback( + &mut self, + thread: ThreadId, + call_time: Time, + callback: TimeoutCallback<'mir, 'tcx>, + ) { + self.timeout_callbacks + .try_insert(thread, TimeoutCallbackInfo { call_time, callback }) + .unwrap(); + } + + /// Unregister the callback for the `thread`. + fn unregister_timeout_callback_if_exists(&mut self, thread: ThreadId) { + self.timeout_callbacks.remove(&thread); + } + + /// Get a callback that is ready to be called. + fn get_ready_callback( + &mut self, + clock: &Clock, + ) -> Option<(ThreadId, TimeoutCallback<'mir, 'tcx>)> { + // We iterate over all threads in the order of their indices because + // this allows us to have a deterministic scheduler. + for thread in self.threads.indices() { + match self.timeout_callbacks.entry(thread) { + Entry::Occupied(entry) => + if entry.get().call_time.get_wait_time(clock) == Duration::new(0, 0) { + return Some((thread, entry.remove().callback)); + }, + Entry::Vacant(_) => {} + } + } + None + } + + /// Wakes up threads joining on the active one and deallocates thread-local statics. + /// The `AllocId` that can now be freed are returned. + fn thread_terminated( + &mut self, + mut data_race: Option<&mut data_race::GlobalState>, + ) -> Vec> { + let mut free_tls_statics = Vec::new(); + { + let mut thread_local_statics = self.thread_local_alloc_ids.borrow_mut(); + thread_local_statics.retain(|&(_def_id, thread), &mut alloc_id| { + if thread != self.active_thread { + // Keep this static around. + return true; + } + // Delete this static from the map and from memory. + // We cannot free directly here as we cannot use `?` in this context. + free_tls_statics.push(alloc_id); + false + }); + } + // Set the thread into a terminated state in the data-race detector. + if let Some(ref mut data_race) = data_race { + data_race.thread_terminated(self); + } + // Check if we need to unblock any threads. + let mut joined_threads = vec![]; // store which threads joined, we'll need it + for (i, thread) in self.threads.iter_enumerated_mut() { + if thread.state == ThreadState::BlockedOnJoin(self.active_thread) { + // The thread has terminated, mark happens-before edge to joining thread + if data_race.is_some() { + joined_threads.push(i); + } + trace!("unblocking {:?} because {:?} terminated", i, self.active_thread); + thread.state = ThreadState::Enabled; + } + } + for &i in &joined_threads { + data_race.as_mut().unwrap().thread_joined(self, i, self.active_thread); + } + free_tls_statics + } + + /// Decide which action to take next and on which thread. + /// + /// The currently implemented scheduling policy is the one that is commonly + /// used in stateless model checkers such as Loom: run the active thread as + /// long as we can and switch only when we have to (the active thread was + /// blocked, terminated, or has explicitly asked to be preempted). + fn schedule(&mut self, clock: &Clock) -> InterpResult<'tcx, SchedulingAction> { + // Check whether the thread has **just** terminated (`check_terminated` + // checks whether the thread has popped all its stack and if yes, sets + // the thread state to terminated). + if self.threads[self.active_thread].check_terminated() { + return Ok(SchedulingAction::ExecuteDtors); + } + // If we get here again and the thread is *still* terminated, there are no more dtors to run. + if self.threads[MAIN_THREAD].state == ThreadState::Terminated { + // The main thread terminated; stop the program. + // We do *not* run TLS dtors of remaining threads, which seems to match rustc behavior. + return Ok(SchedulingAction::Stop); + } + // This thread and the program can keep going. + if self.threads[self.active_thread].state == ThreadState::Enabled + && !self.yield_active_thread + { + // The currently active thread is still enabled, just continue with it. + return Ok(SchedulingAction::ExecuteStep); + } + // The active thread yielded. Let's see if there are any timeouts to take care of. We do + // this *before* running any other thread, to ensure that timeouts "in the past" fire before + // any other thread can take an action. This ensures that for `pthread_cond_timedwait`, "an + // error is returned if [...] the absolute time specified by abstime has already been passed + // at the time of the call". + // + let potential_sleep_time = + self.timeout_callbacks.values().map(|info| info.call_time.get_wait_time(clock)).min(); + if potential_sleep_time == Some(Duration::new(0, 0)) { + return Ok(SchedulingAction::ExecuteTimeoutCallback); + } + // No callbacks scheduled, pick a regular thread to execute. + // The active thread blocked or yielded. So we go search for another enabled thread. + // Crucially, we start searching at the current active thread ID, rather than at 0, since we + // want to avoid always scheduling threads 0 and 1 without ever making progress in thread 2. + // + // `skip(N)` means we start iterating at thread N, so we skip 1 more to start just *after* + // the active thread. Then after that we look at `take(N)`, i.e., the threads *before* the + // active thread. + let threads = self + .threads + .iter_enumerated() + .skip(self.active_thread.index() + 1) + .chain(self.threads.iter_enumerated().take(self.active_thread.index())); + for (id, thread) in threads { + debug_assert_ne!(self.active_thread, id); + if thread.state == ThreadState::Enabled { + self.active_thread = id; + break; + } + } + self.yield_active_thread = false; + if self.threads[self.active_thread].state == ThreadState::Enabled { + return Ok(SchedulingAction::ExecuteStep); + } + // We have not found a thread to execute. + if self.threads.iter().all(|thread| thread.state == ThreadState::Terminated) { + unreachable!("all threads terminated without the main thread terminating?!"); + } else if let Some(sleep_time) = potential_sleep_time { + // All threads are currently blocked, but we have unexecuted + // timeout_callbacks, which may unblock some of the threads. Hence, + // sleep until the first callback. + + clock.sleep(sleep_time); + Ok(SchedulingAction::ExecuteTimeoutCallback) + } else { + throw_machine_stop!(TerminationInfo::Deadlock); + } + } +} + +// Public interface to thread management. +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + /// Get a thread-specific allocation id for the given thread-local static. + /// If needed, allocate a new one. + fn get_or_create_thread_local_alloc( + &mut self, + def_id: DefId, + ) -> InterpResult<'tcx, Pointer> { + let this = self.eval_context_mut(); + let tcx = this.tcx; + if let Some(old_alloc) = this.machine.threads.get_thread_local_alloc_id(def_id) { + // We already have a thread-specific allocation id for this + // thread-local static. + Ok(old_alloc) + } else { + // We need to allocate a thread-specific allocation id for this + // thread-local static. + // First, we compute the initial value for this static. + if tcx.is_foreign_item(def_id) { + throw_unsup_format!("foreign thread-local statics are not supported"); + } + let allocation = tcx.eval_static_initializer(def_id)?; + let mut allocation = allocation.inner().clone(); + // This allocation will be deallocated when the thread dies, so it is not in read-only memory. + allocation.mutability = Mutability::Mut; + // Create a fresh allocation with this content. + let new_alloc = this.allocate_raw_ptr(allocation, MiriMemoryKind::Tls.into())?; + this.machine.threads.set_thread_local_alloc(def_id, new_alloc); + Ok(new_alloc) + } + } + + #[inline] + fn create_thread(&mut self) -> ThreadId { + let this = self.eval_context_mut(); + let id = this.machine.threads.create_thread(); + if let Some(data_race) = &mut this.machine.data_race { + data_race.thread_created(&this.machine.threads, id); + } + id + } + + #[inline] + fn start_thread( + &mut self, + thread: Option>, + start_routine: Pointer>, + start_abi: Abi, + func_arg: ImmTy<'tcx, Provenance>, + ret_layout: TyAndLayout<'tcx>, + ) -> InterpResult<'tcx, ThreadId> { + let this = self.eval_context_mut(); + + // Create the new thread + let new_thread_id = this.create_thread(); + + // Write the current thread-id, switch to the next thread later + // to treat this write operation as occuring on the current thread. + if let Some(thread_info_place) = thread { + this.write_scalar( + Scalar::from_uint(new_thread_id.to_u32(), thread_info_place.layout.size), + &thread_info_place.into(), + )?; + } + + // Finally switch to new thread so that we can push the first stackframe. + // After this all accesses will be treated as occuring in the new thread. + let old_thread_id = this.set_active_thread(new_thread_id); + + // Perform the function pointer load in the new thread frame. + let instance = this.get_ptr_fn(start_routine)?.as_instance()?; + + // Note: the returned value is currently ignored (see the FIXME in + // pthread_join in shims/unix/thread.rs) because the Rust standard library does not use + // it. + let ret_place = this.allocate(ret_layout, MiriMemoryKind::Machine.into())?; + + this.call_function( + instance, + start_abi, + &[*func_arg], + Some(&ret_place.into()), + StackPopCleanup::Root { cleanup: true }, + )?; + + // Restore the old active thread frame. + this.set_active_thread(old_thread_id); + + Ok(new_thread_id) + } + + #[inline] + fn detach_thread( + &mut self, + thread_id: ThreadId, + allow_terminated_joined: bool, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + this.machine.threads.detach_thread(thread_id, allow_terminated_joined) + } + + #[inline] + fn join_thread(&mut self, joined_thread_id: ThreadId) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + this.machine.threads.join_thread(joined_thread_id, this.machine.data_race.as_mut())?; + Ok(()) + } + + #[inline] + fn join_thread_exclusive(&mut self, joined_thread_id: ThreadId) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + this.machine + .threads + .join_thread_exclusive(joined_thread_id, this.machine.data_race.as_mut())?; + Ok(()) + } + + #[inline] + fn set_active_thread(&mut self, thread_id: ThreadId) -> ThreadId { + let this = self.eval_context_mut(); + this.machine.threads.set_active_thread_id(thread_id) + } + + #[inline] + fn get_active_thread(&self) -> ThreadId { + let this = self.eval_context_ref(); + this.machine.threads.get_active_thread_id() + } + + #[inline] + fn active_thread_mut(&mut self) -> &mut Thread<'mir, 'tcx> { + let this = self.eval_context_mut(); + this.machine.threads.active_thread_mut() + } + + #[inline] + fn active_thread_ref(&self) -> &Thread<'mir, 'tcx> { + let this = self.eval_context_ref(); + this.machine.threads.active_thread_ref() + } + + #[inline] + fn get_total_thread_count(&self) -> usize { + let this = self.eval_context_ref(); + this.machine.threads.get_total_thread_count() + } + + #[inline] + fn has_terminated(&self, thread_id: ThreadId) -> bool { + let this = self.eval_context_ref(); + this.machine.threads.has_terminated(thread_id) + } + + #[inline] + fn have_all_terminated(&self) -> bool { + let this = self.eval_context_ref(); + this.machine.threads.have_all_terminated() + } + + #[inline] + fn enable_thread(&mut self, thread_id: ThreadId) { + let this = self.eval_context_mut(); + this.machine.threads.enable_thread(thread_id); + } + + #[inline] + fn active_thread_stack(&self) -> &[Frame<'mir, 'tcx, Provenance, FrameData<'tcx>>] { + let this = self.eval_context_ref(); + this.machine.threads.active_thread_stack() + } + + #[inline] + fn active_thread_stack_mut( + &mut self, + ) -> &mut Vec>> { + let this = self.eval_context_mut(); + this.machine.threads.active_thread_stack_mut() + } + + #[inline] + fn set_thread_name(&mut self, thread: ThreadId, new_thread_name: Vec) { + let this = self.eval_context_mut(); + this.machine.threads.set_thread_name(thread, new_thread_name); + } + + #[inline] + fn set_thread_name_wide(&mut self, thread: ThreadId, new_thread_name: &[u16]) { + let this = self.eval_context_mut(); + + // The Windows `GetThreadDescription` shim to get the thread name isn't implemented, so being lossy is okay. + // This is only read by diagnostics, which already use `from_utf8_lossy`. + this.machine + .threads + .set_thread_name(thread, String::from_utf16_lossy(new_thread_name).into_bytes()); + } + + #[inline] + fn get_thread_name<'c>(&'c self, thread: ThreadId) -> &'c [u8] + where + 'mir: 'c, + { + let this = self.eval_context_ref(); + this.machine.threads.get_thread_name(thread) + } + + #[inline] + fn block_thread(&mut self, thread: ThreadId) { + let this = self.eval_context_mut(); + this.machine.threads.block_thread(thread); + } + + #[inline] + fn unblock_thread(&mut self, thread: ThreadId) { + let this = self.eval_context_mut(); + this.machine.threads.unblock_thread(thread); + } + + #[inline] + fn yield_active_thread(&mut self) { + let this = self.eval_context_mut(); + this.machine.threads.yield_active_thread(); + } + + #[inline] + fn maybe_preempt_active_thread(&mut self) { + use rand::Rng as _; + + let this = self.eval_context_mut(); + if this.machine.rng.get_mut().gen_bool(this.machine.preemption_rate) { + this.yield_active_thread(); + } + } + + #[inline] + fn register_timeout_callback( + &mut self, + thread: ThreadId, + call_time: Time, + callback: TimeoutCallback<'mir, 'tcx>, + ) { + let this = self.eval_context_mut(); + if !this.machine.communicate() && matches!(call_time, Time::RealTime(..)) { + panic!("cannot have `RealTime` callback with isolation enabled!") + } + this.machine.threads.register_timeout_callback(thread, call_time, callback); + } + + #[inline] + fn unregister_timeout_callback_if_exists(&mut self, thread: ThreadId) { + let this = self.eval_context_mut(); + this.machine.threads.unregister_timeout_callback_if_exists(thread); + } + + /// Execute a timeout callback on the callback's thread. + #[inline] + fn run_timeout_callback(&mut self) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + let (thread, callback) = if let Some((thread, callback)) = + this.machine.threads.get_ready_callback(&this.machine.clock) + { + (thread, callback) + } else { + // get_ready_callback can return None if the computer's clock + // was shifted after calling the scheduler and before the call + // to get_ready_callback (see issue + // https://github.com/rust-lang/miri/issues/1763). In this case, + // just do nothing, which effectively just returns to the + // scheduler. + return Ok(()); + }; + // This back-and-forth with `set_active_thread` is here because of two + // design decisions: + // 1. Make the caller and not the callback responsible for changing + // thread. + // 2. Make the scheduler the only place that can change the active + // thread. + let old_thread = this.set_active_thread(thread); + callback(this)?; + this.set_active_thread(old_thread); + Ok(()) + } + + /// Decide which action to take next and on which thread. + #[inline] + fn schedule(&mut self) -> InterpResult<'tcx, SchedulingAction> { + let this = self.eval_context_mut(); + this.machine.threads.schedule(&this.machine.clock) + } + + /// Handles thread termination of the active thread: wakes up threads joining on this one, + /// and deallocated thread-local statics. + /// + /// This is called from `tls.rs` after handling the TLS dtors. + #[inline] + fn thread_terminated(&mut self) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + for ptr in this.machine.threads.thread_terminated(this.machine.data_race.as_mut()) { + this.deallocate_ptr(ptr.into(), None, MiriMemoryKind::Tls.into())?; + } + Ok(()) + } +} diff --git a/src/tools/miri/src/concurrency/vector_clock.rs b/src/tools/miri/src/concurrency/vector_clock.rs new file mode 100644 index 0000000000000..32449f8eb1884 --- /dev/null +++ b/src/tools/miri/src/concurrency/vector_clock.rs @@ -0,0 +1,470 @@ +use rustc_index::vec::Idx; +use smallvec::SmallVec; +use std::{cmp::Ordering, fmt::Debug, ops::Index}; + +/// A vector clock index, this is associated with a thread id +/// but in some cases one vector index may be shared with +/// multiple thread ids if it safe to do so. +#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)] +pub struct VectorIdx(u32); + +impl VectorIdx { + #[inline(always)] + pub fn to_u32(self) -> u32 { + self.0 + } + + pub const MAX_INDEX: VectorIdx = VectorIdx(u32::MAX); +} + +impl Idx for VectorIdx { + #[inline] + fn new(idx: usize) -> Self { + VectorIdx(u32::try_from(idx).unwrap()) + } + + #[inline] + fn index(self) -> usize { + usize::try_from(self.0).unwrap() + } +} + +impl From for VectorIdx { + #[inline] + fn from(id: u32) -> Self { + Self(id) + } +} + +/// The size of the vector-clock to store inline +/// clock vectors larger than this will be stored on the heap +const SMALL_VECTOR: usize = 4; + +/// The type of the time-stamps recorded in the data-race detector +/// set to a type of unsigned integer +pub type VTimestamp = u32; + +/// A vector clock for detecting data-races, this is conceptually +/// a map from a vector index (and thus a thread id) to a timestamp. +/// The compare operations require that the invariant that the last +/// element in the internal timestamp slice must not be a 0, hence +/// all zero vector clocks are always represented by the empty slice; +/// and allows for the implementation of compare operations to short +/// circuit the calculation and return the correct result faster, +/// also this means that there is only one unique valid length +/// for each set of vector clock values and hence the PartialEq +/// and Eq derivations are correct. +#[derive(PartialEq, Eq, Default, Debug)] +pub struct VClock(SmallVec<[VTimestamp; SMALL_VECTOR]>); + +impl VClock { + /// Create a new vector-clock containing all zeros except + /// for a value at the given index + pub fn new_with_index(index: VectorIdx, timestamp: VTimestamp) -> VClock { + let len = index.index() + 1; + let mut vec = smallvec::smallvec![0; len]; + vec[index.index()] = timestamp; + VClock(vec) + } + + /// Load the internal timestamp slice in the vector clock + #[inline] + pub fn as_slice(&self) -> &[VTimestamp] { + self.0.as_slice() + } + + /// Get a mutable slice to the internal vector with minimum `min_len` + /// elements, to preserve invariants this vector must modify + /// the `min_len`-1 nth element to a non-zero value + #[inline] + fn get_mut_with_min_len(&mut self, min_len: usize) -> &mut [VTimestamp] { + if self.0.len() < min_len { + self.0.resize(min_len, 0); + } + assert!(self.0.len() >= min_len); + self.0.as_mut_slice() + } + + /// Increment the vector clock at a known index + /// this will panic if the vector index overflows + #[inline] + pub fn increment_index(&mut self, idx: VectorIdx) { + let idx = idx.index(); + let mut_slice = self.get_mut_with_min_len(idx + 1); + let idx_ref = &mut mut_slice[idx]; + *idx_ref = idx_ref.checked_add(1).expect("Vector clock overflow") + } + + // Join the two vector-clocks together, this + // sets each vector-element to the maximum value + // of that element in either of the two source elements. + pub fn join(&mut self, other: &Self) { + let rhs_slice = other.as_slice(); + let lhs_slice = self.get_mut_with_min_len(rhs_slice.len()); + for (l, &r) in lhs_slice.iter_mut().zip(rhs_slice.iter()) { + *l = r.max(*l); + } + } + + /// Set the element at the current index of the vector + pub fn set_at_index(&mut self, other: &Self, idx: VectorIdx) { + let mut_slice = self.get_mut_with_min_len(idx.index() + 1); + mut_slice[idx.index()] = other[idx]; + } + + /// Set the vector to the all-zero vector + #[inline] + pub fn set_zero_vector(&mut self) { + self.0.clear(); + } + + /// Return if this vector is the all-zero vector + pub fn is_zero_vector(&self) -> bool { + self.0.is_empty() + } +} + +impl Clone for VClock { + fn clone(&self) -> Self { + VClock(self.0.clone()) + } + + // Optimized clone-from, can be removed + // and replaced with a derive once a similar + // optimization is inserted into SmallVec's + // clone implementation. + fn clone_from(&mut self, source: &Self) { + let source_slice = source.as_slice(); + self.0.clear(); + self.0.extend_from_slice(source_slice); + } +} + +impl PartialOrd for VClock { + fn partial_cmp(&self, other: &VClock) -> Option { + // Load the values as slices + let lhs_slice = self.as_slice(); + let rhs_slice = other.as_slice(); + + // Iterate through the combined vector slice continuously updating + // the value of `order` to the current comparison of the vector from + // index 0 to the currently checked index. + // An Equal ordering can be converted into Less or Greater ordering + // on finding an element that is less than or greater than the other + // but if one Greater and one Less element-wise comparison is found + // then no ordering is possible and so directly return an ordering + // of None. + let mut iter = lhs_slice.iter().zip(rhs_slice.iter()); + let mut order = match iter.next() { + Some((lhs, rhs)) => lhs.cmp(rhs), + None => Ordering::Equal, + }; + for (l, r) in iter { + match order { + Ordering::Equal => order = l.cmp(r), + Ordering::Less => + if l > r { + return None; + }, + Ordering::Greater => + if l < r { + return None; + }, + } + } + + // Now test if either left or right have trailing elements, + // by the invariant the trailing elements have at least 1 + // non zero value, so no additional calculation is required + // to determine the result of the PartialOrder. + let l_len = lhs_slice.len(); + let r_len = rhs_slice.len(); + match l_len.cmp(&r_len) { + // Equal means no additional elements: return current order + Ordering::Equal => Some(order), + // Right has at least 1 element > than the implicit 0, + // so the only valid values are Ordering::Less or None. + Ordering::Less => + match order { + Ordering::Less | Ordering::Equal => Some(Ordering::Less), + Ordering::Greater => None, + }, + // Left has at least 1 element > than the implicit 0, + // so the only valid values are Ordering::Greater or None. + Ordering::Greater => + match order { + Ordering::Greater | Ordering::Equal => Some(Ordering::Greater), + Ordering::Less => None, + }, + } + } + + fn lt(&self, other: &VClock) -> bool { + // Load the values as slices + let lhs_slice = self.as_slice(); + let rhs_slice = other.as_slice(); + + // If l_len > r_len then at least one element + // in l_len is > than r_len, therefore the result + // is either Some(Greater) or None, so return false + // early. + let l_len = lhs_slice.len(); + let r_len = rhs_slice.len(); + if l_len <= r_len { + // If any elements on the left are greater than the right + // then the result is None or Some(Greater), both of which + // return false, the earlier test asserts that no elements in the + // extended tail violate this assumption. Otherwise l <= r, finally + // the case where the values are potentially equal needs to be considered + // and false returned as well + let mut equal = l_len == r_len; + for (&l, &r) in lhs_slice.iter().zip(rhs_slice.iter()) { + if l > r { + return false; + } else if l < r { + equal = false; + } + } + !equal + } else { + false + } + } + + fn le(&self, other: &VClock) -> bool { + // Load the values as slices + let lhs_slice = self.as_slice(); + let rhs_slice = other.as_slice(); + + // If l_len > r_len then at least one element + // in l_len is > than r_len, therefore the result + // is either Some(Greater) or None, so return false + // early. + let l_len = lhs_slice.len(); + let r_len = rhs_slice.len(); + if l_len <= r_len { + // If any elements on the left are greater than the right + // then the result is None or Some(Greater), both of which + // return false, the earlier test asserts that no elements in the + // extended tail violate this assumption. Otherwise l <= r + !lhs_slice.iter().zip(rhs_slice.iter()).any(|(&l, &r)| l > r) + } else { + false + } + } + + fn gt(&self, other: &VClock) -> bool { + // Load the values as slices + let lhs_slice = self.as_slice(); + let rhs_slice = other.as_slice(); + + // If r_len > l_len then at least one element + // in r_len is > than l_len, therefore the result + // is either Some(Less) or None, so return false + // early. + let l_len = lhs_slice.len(); + let r_len = rhs_slice.len(); + if l_len >= r_len { + // If any elements on the left are less than the right + // then the result is None or Some(Less), both of which + // return false, the earlier test asserts that no elements in the + // extended tail violate this assumption. Otherwise l >=, finally + // the case where the values are potentially equal needs to be considered + // and false returned as well + let mut equal = l_len == r_len; + for (&l, &r) in lhs_slice.iter().zip(rhs_slice.iter()) { + if l < r { + return false; + } else if l > r { + equal = false; + } + } + !equal + } else { + false + } + } + + fn ge(&self, other: &VClock) -> bool { + // Load the values as slices + let lhs_slice = self.as_slice(); + let rhs_slice = other.as_slice(); + + // If r_len > l_len then at least one element + // in r_len is > than l_len, therefore the result + // is either Some(Less) or None, so return false + // early. + let l_len = lhs_slice.len(); + let r_len = rhs_slice.len(); + if l_len >= r_len { + // If any elements on the left are less than the right + // then the result is None or Some(Less), both of which + // return false, the earlier test asserts that no elements in the + // extended tail violate this assumption. Otherwise l >= r + !lhs_slice.iter().zip(rhs_slice.iter()).any(|(&l, &r)| l < r) + } else { + false + } + } +} + +impl Index for VClock { + type Output = VTimestamp; + + #[inline] + fn index(&self, index: VectorIdx) -> &VTimestamp { + self.as_slice().get(index.to_u32() as usize).unwrap_or(&0) + } +} + +/// Test vector clock ordering operations +/// data-race detection is tested in the external +/// test suite +#[cfg(test)] +mod tests { + + use super::{VClock, VTimestamp, VectorIdx}; + use std::cmp::Ordering; + + #[test] + fn test_equal() { + let mut c1 = VClock::default(); + let mut c2 = VClock::default(); + assert_eq!(c1, c2); + c1.increment_index(VectorIdx(5)); + assert_ne!(c1, c2); + c2.increment_index(VectorIdx(53)); + assert_ne!(c1, c2); + c1.increment_index(VectorIdx(53)); + assert_ne!(c1, c2); + c2.increment_index(VectorIdx(5)); + assert_eq!(c1, c2); + } + + #[test] + fn test_partial_order() { + // Small test + assert_order(&[1], &[1], Some(Ordering::Equal)); + assert_order(&[1], &[2], Some(Ordering::Less)); + assert_order(&[2], &[1], Some(Ordering::Greater)); + assert_order(&[1], &[1, 2], Some(Ordering::Less)); + assert_order(&[2], &[1, 2], None); + + // Misc tests + assert_order(&[400], &[0, 1], None); + + // Large test + assert_order( + &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 0, 0], + Some(Ordering::Equal), + ); + assert_order( + &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 1, 0], + Some(Ordering::Less), + ); + assert_order( + &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11], + &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 0, 0], + Some(Ordering::Greater), + ); + assert_order( + &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11], + &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 1, 0], + None, + ); + assert_order( + &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 9], + &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 0, 0], + Some(Ordering::Less), + ); + assert_order( + &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 9], + &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 1, 0], + Some(Ordering::Less), + ); + } + + fn from_slice(mut slice: &[VTimestamp]) -> VClock { + while let Some(0) = slice.last() { + slice = &slice[..slice.len() - 1] + } + VClock(smallvec::SmallVec::from_slice(slice)) + } + + fn assert_order(l: &[VTimestamp], r: &[VTimestamp], o: Option) { + let l = from_slice(l); + let r = from_slice(r); + + //Test partial_cmp + let compare = l.partial_cmp(&r); + assert_eq!(compare, o, "Invalid comparison\n l: {:?}\n r: {:?}", l, r); + let alt_compare = r.partial_cmp(&l); + assert_eq!( + alt_compare, + o.map(Ordering::reverse), + "Invalid alt comparison\n l: {:?}\n r: {:?}", + l, + r + ); + + //Test operators with faster implementations + assert_eq!( + matches!(compare, Some(Ordering::Less)), + l < r, + "Invalid (<):\n l: {:?}\n r: {:?}", + l, + r + ); + assert_eq!( + matches!(compare, Some(Ordering::Less) | Some(Ordering::Equal)), + l <= r, + "Invalid (<=):\n l: {:?}\n r: {:?}", + l, + r + ); + assert_eq!( + matches!(compare, Some(Ordering::Greater)), + l > r, + "Invalid (>):\n l: {:?}\n r: {:?}", + l, + r + ); + assert_eq!( + matches!(compare, Some(Ordering::Greater) | Some(Ordering::Equal)), + l >= r, + "Invalid (>=):\n l: {:?}\n r: {:?}", + l, + r + ); + assert_eq!( + matches!(alt_compare, Some(Ordering::Less)), + r < l, + "Invalid alt (<):\n l: {:?}\n r: {:?}", + l, + r + ); + assert_eq!( + matches!(alt_compare, Some(Ordering::Less) | Some(Ordering::Equal)), + r <= l, + "Invalid alt (<=):\n l: {:?}\n r: {:?}", + l, + r + ); + assert_eq!( + matches!(alt_compare, Some(Ordering::Greater)), + r > l, + "Invalid alt (>):\n l: {:?}\n r: {:?}", + l, + r + ); + assert_eq!( + matches!(alt_compare, Some(Ordering::Greater) | Some(Ordering::Equal)), + r >= l, + "Invalid alt (>=):\n l: {:?}\n r: {:?}", + l, + r + ); + } +} diff --git a/src/tools/miri/src/concurrency/weak_memory.rs b/src/tools/miri/src/concurrency/weak_memory.rs new file mode 100644 index 0000000000000..bac403e9ec7b1 --- /dev/null +++ b/src/tools/miri/src/concurrency/weak_memory.rs @@ -0,0 +1,630 @@ +//! Implementation of C++11-consistent weak memory emulation using store buffers +//! based on Dynamic Race Detection for C++ ("the paper"): +//! +//! +//! This implementation will never generate weak memory behaviours forbidden by the C++11 model, +//! but it is incapable of producing all possible weak behaviours allowed by the model. There are +//! certain weak behaviours observable on real hardware but not while using this. +//! +//! Note that this implementation does not fully take into account of C++20's memory model revision to SC accesses +//! and fences introduced by P0668 (). +//! This implementation is not fully correct under the revised C++20 model and may generate behaviours C++20 +//! disallows (). +//! +//! A modification is made to the paper's model to partially address C++20 changes. +//! Specifically, if an SC load reads from an atomic store of any ordering, then a later SC load cannot read from +//! an earlier store in the location's modification order. This is to prevent creating a backwards S edge from the second +//! load to the first, as a result of C++20's coherence-ordered before rules. +//! +//! Rust follows the C++20 memory model (except for the Consume ordering and some operations not performable through C++'s +//! std::atomic API). It is therefore possible for this implementation to generate behaviours never observable when the +//! same program is compiled and run natively. Unfortunately, no literature exists at the time of writing which proposes +//! an implementable and C++20-compatible relaxed memory model that supports all atomic operation existing in Rust. The closest one is +//! A Promising Semantics for Relaxed-Memory Concurrency by Jeehoon Kang et al. () +//! However, this model lacks SC accesses and is therefore unusable by Miri (SC accesses are everywhere in library code). +//! +//! If you find anything that proposes a relaxed memory model that is C++20-consistent, supports all orderings Rust's atomic accesses +//! and fences accept, and is implementable (with operational semanitcs), please open a GitHub issue! +//! +//! One characteristic of this implementation, in contrast to some other notable operational models such as ones proposed in +//! Taming Release-Acquire Consistency by Ori Lahav et al. () or Promising Semantics noted above, +//! is that this implementation does not require each thread to hold an isolated view of the entire memory. Here, store buffers are per-location +//! and shared across all threads. This is more memory efficient but does require store elements (representing writes to a location) to record +//! information about reads, whereas in the other two models it is the other way round: reads points to the write it got its value from. +//! Additionally, writes in our implementation do not have globally unique timestamps attached. In the other two models this timestamp is +//! used to make sure a value in a thread's view is not overwritten by a write that occured earlier than the one in the existing view. +//! In our implementation, this is detected using read information attached to store elements, as there is no data strucutre representing reads. +//! +//! The C++ memory model is built around the notion of an 'atomic object', so it would be natural +//! to attach store buffers to atomic objects. However, Rust follows LLVM in that it only has +//! 'atomic accesses'. Therefore Miri cannot know when and where atomic 'objects' are being +//! created or destroyed, to manage its store buffers. Instead, we hence lazily create an +//! atomic object on the first atomic access to a given region, and we destroy that object +//! on the next non-atomic or imperfectly overlapping atomic access to that region. +//! These lazy (de)allocations happen in memory_accessed() on non-atomic accesses, and +//! get_or_create_store_buffer() on atomic accesses. This mostly works well, but it does +//! lead to some issues (). +//! +//! One consequence of this difference is that safe/sound Rust allows for more operations on atomic locations +//! than the C++20 atomic API was intended to allow, such as non-atomically accessing +//! a previously atomically accessed location, or accessing previously atomically accessed locations with a differently sized operation +//! (such as accessing the top 16 bits of an AtomicU32). These senarios are generally undiscussed in formalisations of C++ memory model. +//! In Rust, these operations can only be done through a `&mut AtomicFoo` reference or one derived from it, therefore these operations +//! can only happen after all previous accesses on the same locations. This implementation is adapted to allow these operations. +//! A mixed atomicity read that races with writes, or a write that races with reads or writes will still cause UBs to be thrown. +//! Mixed size atomic accesses must not race with any other atomic access, whether read or write, or a UB will be thrown. +//! You can refer to test cases in weak_memory/extra_cpp.rs and weak_memory/extra_cpp_unsafe.rs for examples of these operations. + +// Our and the author's own implementation (tsan11) of the paper have some deviations from the provided operational semantics in §5.3: +// 1. In the operational semantics, store elements keep a copy of the atomic object's vector clock (AtomicCellClocks::sync_vector in miri), +// but this is not used anywhere so it's omitted here. +// +// 2. In the operational semantics, each store element keeps the timestamp of a thread when it loads from the store. +// If the same thread loads from the same store element multiple times, then the timestamps at all loads are saved in a list of load elements. +// This is not necessary as later loads by the same thread will always have greater timetstamp values, so we only need to record the timestamp of the first +// load by each thread. This optimisation is done in tsan11 +// (https://github.com/ChrisLidbury/tsan11/blob/ecbd6b81e9b9454e01cba78eb9d88684168132c7/lib/tsan/rtl/tsan_relaxed.h#L35-L37) +// and here. +// +// 3. §4.5 of the paper wants an SC store to mark all existing stores in the buffer that happens before it +// as SC. This is not done in the operational semantics but implemented correctly in tsan11 +// (https://github.com/ChrisLidbury/tsan11/blob/ecbd6b81e9b9454e01cba78eb9d88684168132c7/lib/tsan/rtl/tsan_relaxed.cc#L160-L167) +// and here. +// +// 4. W_SC ; R_SC case requires the SC load to ignore all but last store maked SC (stores not marked SC are not +// affected). But this rule is applied to all loads in ReadsFromSet from the paper (last two lines of code), not just SC load. +// This is implemented correctly in tsan11 +// (https://github.com/ChrisLidbury/tsan11/blob/ecbd6b81e9b9454e01cba78eb9d88684168132c7/lib/tsan/rtl/tsan_relaxed.cc#L295) +// and here. + +use std::{ + cell::{Ref, RefCell}, + collections::VecDeque, +}; + +use rustc_const_eval::interpret::{alloc_range, AllocRange, InterpResult, MPlaceTy, Scalar}; +use rustc_data_structures::fx::FxHashMap; + +use crate::*; + +use super::{ + data_race::{GlobalState as DataRaceState, ThreadClockSet}, + range_object_map::{AccessType, RangeObjectMap}, + vector_clock::{VClock, VTimestamp, VectorIdx}, +}; + +pub type AllocExtra = StoreBufferAlloc; + +// Each store buffer must be bounded otherwise it will grow indefinitely. +// However, bounding the store buffer means restricting the amount of weak +// behaviours observable. The author picked 128 as a good tradeoff +// so we follow them here. +const STORE_BUFFER_LIMIT: usize = 128; + +#[derive(Debug, Clone)] +pub struct StoreBufferAlloc { + /// Store buffer of each atomic object in this allocation + // Behind a RefCell because we need to allocate/remove on read access + store_buffers: RefCell>, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub(super) struct StoreBuffer { + // Stores to this location in modification order + buffer: VecDeque, +} + +/// Whether a load returned the latest value or not. +#[derive(PartialEq, Eq)] +enum LoadRecency { + Latest, + Outdated, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct StoreElement { + /// The identifier of the vector index, corresponding to a thread + /// that performed the store. + store_index: VectorIdx, + + /// Whether this store is SC. + is_seqcst: bool, + + /// The timestamp of the storing thread when it performed the store + timestamp: VTimestamp, + /// The value of this store + // FIXME: this means the store must be fully initialized; + // we will have to change this if we want to support atomics on + // (partially) uninitialized data. + val: Scalar, + + /// Metadata about loads from this store element, + /// behind a RefCell to keep load op take &self + load_info: RefCell, +} + +#[derive(Debug, Clone, PartialEq, Eq, Default)] +struct LoadInfo { + /// Timestamp of first loads from this store element by each thread + timestamps: FxHashMap, + /// Whether this store element has been read by an SC load + sc_loaded: bool, +} + +impl StoreBufferAlloc { + pub fn new_allocation() -> Self { + Self { store_buffers: RefCell::new(RangeObjectMap::new()) } + } + + /// Checks if the range imperfectly overlaps with existing buffers + /// Used to determine if mixed-size atomic accesses + fn is_overlapping(&self, range: AllocRange) -> bool { + let buffers = self.store_buffers.borrow(); + let access_type = buffers.access_type(range); + matches!(access_type, AccessType::ImperfectlyOverlapping(_)) + } + + /// When a non-atomic access happens on a location that has been atomically accessed + /// before without data race, we can determine that the non-atomic access fully happens + /// after all the prior atomic accesses so the location no longer needs to exhibit + /// any weak memory behaviours until further atomic accesses. + pub fn memory_accessed(&self, range: AllocRange, global: &DataRaceState) { + if !global.ongoing_action_data_race_free() { + let mut buffers = self.store_buffers.borrow_mut(); + let access_type = buffers.access_type(range); + match access_type { + AccessType::PerfectlyOverlapping(pos) => { + buffers.remove_from_pos(pos); + } + AccessType::ImperfectlyOverlapping(pos_range) => { + buffers.remove_pos_range(pos_range); + } + AccessType::Empty(_) => { + // The range had no weak behaivours attached, do nothing + } + } + } + } + + /// Gets a store buffer associated with an atomic object in this allocation, + /// or creates one with the specified initial value if no atomic object exists yet. + fn get_or_create_store_buffer<'tcx>( + &self, + range: AllocRange, + init: Scalar, + ) -> InterpResult<'tcx, Ref<'_, StoreBuffer>> { + let access_type = self.store_buffers.borrow().access_type(range); + let pos = match access_type { + AccessType::PerfectlyOverlapping(pos) => pos, + AccessType::Empty(pos) => { + let mut buffers = self.store_buffers.borrow_mut(); + buffers.insert_at_pos(pos, range, StoreBuffer::new(init)); + pos + } + AccessType::ImperfectlyOverlapping(pos_range) => { + // Once we reach here we would've already checked that this access is not racy + let mut buffers = self.store_buffers.borrow_mut(); + buffers.remove_pos_range(pos_range.clone()); + buffers.insert_at_pos(pos_range.start, range, StoreBuffer::new(init)); + pos_range.start + } + }; + Ok(Ref::map(self.store_buffers.borrow(), |buffer| &buffer[pos])) + } + + /// Gets a mutable store buffer associated with an atomic object in this allocation + fn get_or_create_store_buffer_mut<'tcx>( + &mut self, + range: AllocRange, + init: Scalar, + ) -> InterpResult<'tcx, &mut StoreBuffer> { + let buffers = self.store_buffers.get_mut(); + let access_type = buffers.access_type(range); + let pos = match access_type { + AccessType::PerfectlyOverlapping(pos) => pos, + AccessType::Empty(pos) => { + buffers.insert_at_pos(pos, range, StoreBuffer::new(init)); + pos + } + AccessType::ImperfectlyOverlapping(pos_range) => { + buffers.remove_pos_range(pos_range.clone()); + buffers.insert_at_pos(pos_range.start, range, StoreBuffer::new(init)); + pos_range.start + } + }; + Ok(&mut buffers[pos]) + } +} + +impl<'mir, 'tcx: 'mir> StoreBuffer { + fn new(init: Scalar) -> Self { + let mut buffer = VecDeque::new(); + buffer.reserve(STORE_BUFFER_LIMIT); + let mut ret = Self { buffer }; + let store_elem = StoreElement { + // The thread index and timestamp of the initialisation write + // are never meaningfully used, so it's fine to leave them as 0 + store_index: VectorIdx::from(0), + timestamp: 0, + val: init, + is_seqcst: false, + load_info: RefCell::new(LoadInfo::default()), + }; + ret.buffer.push_back(store_elem); + ret + } + + /// Reads from the last store in modification order + fn read_from_last_store( + &self, + global: &DataRaceState, + thread_mgr: &ThreadManager<'_, '_>, + is_seqcst: bool, + ) { + let store_elem = self.buffer.back(); + if let Some(store_elem) = store_elem { + let (index, clocks) = global.current_thread_state(thread_mgr); + store_elem.load_impl(index, &clocks, is_seqcst); + } + } + + fn buffered_read( + &self, + global: &DataRaceState, + thread_mgr: &ThreadManager<'_, '_>, + is_seqcst: bool, + rng: &mut (impl rand::Rng + ?Sized), + validate: impl FnOnce() -> InterpResult<'tcx>, + ) -> InterpResult<'tcx, (Scalar, LoadRecency)> { + // Having a live borrow to store_buffer while calling validate_atomic_load is fine + // because the race detector doesn't touch store_buffer + + let (store_elem, recency) = { + // The `clocks` we got here must be dropped before calling validate_atomic_load + // as the race detector will update it + let (.., clocks) = global.current_thread_state(thread_mgr); + // Load from a valid entry in the store buffer + self.fetch_store(is_seqcst, &clocks, &mut *rng) + }; + + // Unlike in buffered_atomic_write, thread clock updates have to be done + // after we've picked a store element from the store buffer, as presented + // in ATOMIC LOAD rule of the paper. This is because fetch_store + // requires access to ThreadClockSet.clock, which is updated by the race detector + validate()?; + + let (index, clocks) = global.current_thread_state(thread_mgr); + let loaded = store_elem.load_impl(index, &clocks, is_seqcst); + Ok((loaded, recency)) + } + + fn buffered_write( + &mut self, + val: Scalar, + global: &DataRaceState, + thread_mgr: &ThreadManager<'_, '_>, + is_seqcst: bool, + ) -> InterpResult<'tcx> { + let (index, clocks) = global.current_thread_state(thread_mgr); + + self.store_impl(val, index, &clocks.clock, is_seqcst); + Ok(()) + } + + #[allow(clippy::if_same_then_else, clippy::needless_bool)] + /// Selects a valid store element in the buffer. + fn fetch_store( + &self, + is_seqcst: bool, + clocks: &ThreadClockSet, + rng: &mut R, + ) -> (&StoreElement, LoadRecency) { + use rand::seq::IteratorRandom; + let mut found_sc = false; + // FIXME: we want an inclusive take_while (stops after a false predicate, but + // includes the element that gave the false), but such function doesn't yet + // exist in the standard libary https://github.com/rust-lang/rust/issues/62208 + // so we have to hack around it with keep_searching + let mut keep_searching = true; + let candidates = self + .buffer + .iter() + .rev() + .take_while(move |&store_elem| { + if !keep_searching { + return false; + } + + keep_searching = if store_elem.timestamp <= clocks.clock[store_elem.store_index] { + // CoWR: if a store happens-before the current load, + // then we can't read-from anything earlier in modification order. + // C++20 §6.9.2.2 [intro.races] paragraph 18 + false + } else if store_elem.load_info.borrow().timestamps.iter().any( + |(&load_index, &load_timestamp)| load_timestamp <= clocks.clock[load_index], + ) { + // CoRR: if there was a load from this store which happened-before the current load, + // then we cannot read-from anything earlier in modification order. + // C++20 §6.9.2.2 [intro.races] paragraph 16 + false + } else if store_elem.timestamp <= clocks.fence_seqcst[store_elem.store_index] { + // The current load, which may be sequenced-after an SC fence, cannot read-before + // the last store sequenced-before an SC fence in another thread. + // C++17 §32.4 [atomics.order] paragraph 6 + false + } else if store_elem.timestamp <= clocks.write_seqcst[store_elem.store_index] + && store_elem.is_seqcst + { + // The current non-SC load, which may be sequenced-after an SC fence, + // cannot read-before the last SC store executed before the fence. + // C++17 §32.4 [atomics.order] paragraph 4 + false + } else if is_seqcst + && store_elem.timestamp <= clocks.read_seqcst[store_elem.store_index] + { + // The current SC load cannot read-before the last store sequenced-before + // the last SC fence. + // C++17 §32.4 [atomics.order] paragraph 5 + false + } else if is_seqcst && store_elem.load_info.borrow().sc_loaded { + // The current SC load cannot read-before a store that an earlier SC load has observed. + // See https://github.com/rust-lang/miri/issues/2301#issuecomment-1222720427 + // Consequences of C++20 §31.4 [atomics.order] paragraph 3.1, 3.3 (coherence-ordered before) + // and 4.1 (coherence-ordered before between SC makes global total order S) + false + } else { + true + }; + + true + }) + .filter(|&store_elem| { + if is_seqcst && store_elem.is_seqcst { + // An SC load needs to ignore all but last store maked SC (stores not marked SC are not + // affected) + let include = !found_sc; + found_sc = true; + include + } else { + true + } + }); + + let chosen = candidates.choose(rng).expect("store buffer cannot be empty"); + if std::ptr::eq(chosen, self.buffer.back().expect("store buffer cannot be empty")) { + (chosen, LoadRecency::Latest) + } else { + (chosen, LoadRecency::Outdated) + } + } + + /// ATOMIC STORE IMPL in the paper (except we don't need the location's vector clock) + fn store_impl( + &mut self, + val: Scalar, + index: VectorIdx, + thread_clock: &VClock, + is_seqcst: bool, + ) { + let store_elem = StoreElement { + store_index: index, + timestamp: thread_clock[index], + // In the language provided in the paper, an atomic store takes the value from a + // non-atomic memory location. + // But we already have the immediate value here so we don't need to do the memory + // access + val, + is_seqcst, + load_info: RefCell::new(LoadInfo::default()), + }; + self.buffer.push_back(store_elem); + if self.buffer.len() > STORE_BUFFER_LIMIT { + self.buffer.pop_front(); + } + if is_seqcst { + // Every store that happens before this needs to be marked as SC + // so that in a later SC load, only the last SC store (i.e. this one) or stores that + // aren't ordered by hb with the last SC is picked. + self.buffer.iter_mut().rev().for_each(|elem| { + if elem.timestamp <= thread_clock[elem.store_index] { + elem.is_seqcst = true; + } + }) + } + } +} + +impl StoreElement { + /// ATOMIC LOAD IMPL in the paper + /// Unlike the operational semantics in the paper, we don't need to keep track + /// of the thread timestamp for every single load. Keeping track of the first (smallest) + /// timestamp of each thread that has loaded from a store is sufficient: if the earliest + /// load of another thread happens before the current one, then we must stop searching the store + /// buffer regardless of subsequent loads by the same thread; if the earliest load of another + /// thread doesn't happen before the current one, then no subsequent load by the other thread + /// can happen before the current one. + fn load_impl( + &self, + index: VectorIdx, + clocks: &ThreadClockSet, + is_seqcst: bool, + ) -> Scalar { + let mut load_info = self.load_info.borrow_mut(); + load_info.sc_loaded |= is_seqcst; + let _ = load_info.timestamps.try_insert(index, clocks.clock[index]); + self.val + } +} + +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>: + crate::MiriInterpCxExt<'mir, 'tcx> +{ + // If weak memory emulation is enabled, check if this atomic op imperfectly overlaps with a previous + // atomic read or write. If it does, then we require it to be ordered (non-racy) with all previous atomic + // accesses on all the bytes in range + fn validate_overlapping_atomic( + &self, + place: &MPlaceTy<'tcx, Provenance>, + ) -> InterpResult<'tcx> { + let this = self.eval_context_ref(); + let (alloc_id, base_offset, ..) = this.ptr_get_alloc_id(place.ptr)?; + if let crate::AllocExtra { + weak_memory: Some(alloc_buffers), + data_race: Some(alloc_clocks), + .. + } = this.get_alloc_extra(alloc_id)? + { + let range = alloc_range(base_offset, place.layout.size); + if alloc_buffers.is_overlapping(range) + && !alloc_clocks.race_free_with_atomic( + range, + this.machine.data_race.as_ref().unwrap(), + &this.machine.threads, + ) + { + throw_unsup_format!( + "racy imperfectly overlapping atomic access is not possible in the C++20 memory model, and not supported by Miri's weak memory emulation" + ); + } + } + Ok(()) + } + + fn buffered_atomic_rmw( + &mut self, + new_val: Scalar, + place: &MPlaceTy<'tcx, Provenance>, + atomic: AtomicRwOrd, + init: Scalar, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + let (alloc_id, base_offset, ..) = this.ptr_get_alloc_id(place.ptr)?; + if let ( + crate::AllocExtra { weak_memory: Some(alloc_buffers), .. }, + crate::MiriMachine { data_race: Some(global), threads, .. }, + ) = this.get_alloc_extra_mut(alloc_id)? + { + if atomic == AtomicRwOrd::SeqCst { + global.sc_read(threads); + global.sc_write(threads); + } + let range = alloc_range(base_offset, place.layout.size); + let buffer = alloc_buffers.get_or_create_store_buffer_mut(range, init)?; + buffer.read_from_last_store(global, threads, atomic == AtomicRwOrd::SeqCst); + buffer.buffered_write(new_val, global, threads, atomic == AtomicRwOrd::SeqCst)?; + } + Ok(()) + } + + fn buffered_atomic_read( + &self, + place: &MPlaceTy<'tcx, Provenance>, + atomic: AtomicReadOrd, + latest_in_mo: Scalar, + validate: impl FnOnce() -> InterpResult<'tcx>, + ) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_ref(); + if let Some(global) = &this.machine.data_race { + let (alloc_id, base_offset, ..) = this.ptr_get_alloc_id(place.ptr)?; + if let Some(alloc_buffers) = this.get_alloc_extra(alloc_id)?.weak_memory.as_ref() { + if atomic == AtomicReadOrd::SeqCst { + global.sc_read(&this.machine.threads); + } + let mut rng = this.machine.rng.borrow_mut(); + let buffer = alloc_buffers.get_or_create_store_buffer( + alloc_range(base_offset, place.layout.size), + latest_in_mo, + )?; + let (loaded, recency) = buffer.buffered_read( + global, + &this.machine.threads, + atomic == AtomicReadOrd::SeqCst, + &mut *rng, + validate, + )?; + if global.track_outdated_loads && recency == LoadRecency::Outdated { + this.emit_diagnostic(NonHaltingDiagnostic::WeakMemoryOutdatedLoad); + } + + return Ok(loaded); + } + } + + // Race detector or weak memory disabled, simply read the latest value + validate()?; + Ok(latest_in_mo) + } + + fn buffered_atomic_write( + &mut self, + val: Scalar, + dest: &MPlaceTy<'tcx, Provenance>, + atomic: AtomicWriteOrd, + init: Scalar, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + let (alloc_id, base_offset, ..) = this.ptr_get_alloc_id(dest.ptr)?; + if let ( + crate::AllocExtra { weak_memory: Some(alloc_buffers), .. }, + crate::MiriMachine { data_race: Some(global), threads, .. }, + ) = this.get_alloc_extra_mut(alloc_id)? + { + if atomic == AtomicWriteOrd::SeqCst { + global.sc_write(threads); + } + + // UGLY HACK: in write_scalar_atomic() we don't know the value before our write, + // so init == val always. If the buffer is fresh then we would've duplicated an entry, + // so we need to remove it. + // See https://github.com/rust-lang/miri/issues/2164 + let was_empty = matches!( + alloc_buffers + .store_buffers + .borrow() + .access_type(alloc_range(base_offset, dest.layout.size)), + AccessType::Empty(_) + ); + let buffer = alloc_buffers + .get_or_create_store_buffer_mut(alloc_range(base_offset, dest.layout.size), init)?; + if was_empty { + buffer.buffer.pop_front(); + } + + buffer.buffered_write(val, global, threads, atomic == AtomicWriteOrd::SeqCst)?; + } + + // Caller should've written to dest with the vanilla scalar write, we do nothing here + Ok(()) + } + + /// Caller should never need to consult the store buffer for the latest value. + /// This function is used exclusively for failed atomic_compare_exchange_scalar + /// to perform load_impl on the latest store element + fn perform_read_on_buffered_latest( + &self, + place: &MPlaceTy<'tcx, Provenance>, + atomic: AtomicReadOrd, + init: Scalar, + ) -> InterpResult<'tcx> { + let this = self.eval_context_ref(); + + if let Some(global) = &this.machine.data_race { + if atomic == AtomicReadOrd::SeqCst { + global.sc_read(&this.machine.threads); + } + let size = place.layout.size; + let (alloc_id, base_offset, ..) = this.ptr_get_alloc_id(place.ptr)?; + if let Some(alloc_buffers) = this.get_alloc_extra(alloc_id)?.weak_memory.as_ref() { + let buffer = alloc_buffers + .get_or_create_store_buffer(alloc_range(base_offset, size), init)?; + buffer.read_from_last_store( + global, + &this.machine.threads, + atomic == AtomicReadOrd::SeqCst, + ); + } + } + Ok(()) + } +} diff --git a/src/tools/miri/src/diagnostics.rs b/src/tools/miri/src/diagnostics.rs new file mode 100644 index 0000000000000..868c85c04a88d --- /dev/null +++ b/src/tools/miri/src/diagnostics.rs @@ -0,0 +1,500 @@ +use std::fmt; +use std::num::NonZeroU64; + +use log::trace; + +use rustc_span::{source_map::DUMMY_SP, SpanData, Symbol}; +use rustc_target::abi::{Align, Size}; + +use crate::stacked_borrows::{diagnostics::TagHistory, AccessKind}; +use crate::*; + +/// Details of premature program termination. +pub enum TerminationInfo { + Exit(i64), + Abort(String), + UnsupportedInIsolation(String), + StackedBorrowsUb { + msg: String, + help: Option, + history: Option, + }, + Int2PtrWithStrictProvenance, + Deadlock, + MultipleSymbolDefinitions { + link_name: Symbol, + first: SpanData, + first_crate: Symbol, + second: SpanData, + second_crate: Symbol, + }, + SymbolShimClashing { + link_name: Symbol, + span: SpanData, + }, +} + +impl fmt::Display for TerminationInfo { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use TerminationInfo::*; + match self { + Exit(code) => write!(f, "the evaluated program completed with exit code {code}"), + Abort(msg) => write!(f, "{msg}"), + UnsupportedInIsolation(msg) => write!(f, "{msg}"), + Int2PtrWithStrictProvenance => + write!( + f, + "integer-to-pointer casts and `ptr::from_exposed_addr` are not supported with `-Zmiri-strict-provenance`" + ), + StackedBorrowsUb { msg, .. } => write!(f, "{msg}"), + Deadlock => write!(f, "the evaluated program deadlocked"), + MultipleSymbolDefinitions { link_name, .. } => + write!(f, "multiple definitions of symbol `{link_name}`"), + SymbolShimClashing { link_name, .. } => + write!(f, "found `{link_name}` symbol definition that clashes with a built-in shim",), + } + } +} + +impl MachineStopType for TerminationInfo {} + +/// Miri specific diagnostics +pub enum NonHaltingDiagnostic { + CreatedPointerTag(NonZeroU64, Option<(AllocId, AllocRange)>), + /// This `Item` was popped from the borrow stack, either due to an access with the given tag or + /// a deallocation when the second argument is `None`. + PoppedPointerTag(Item, Option<(ProvenanceExtra, AccessKind)>), + CreatedCallId(CallId), + CreatedAlloc(AllocId, Size, Align, MemoryKind), + FreedAlloc(AllocId), + RejectedIsolatedOp(String), + ProgressReport { + block_count: u64, // how many basic blocks have been run so far + }, + Int2Ptr { + details: bool, + }, + WeakMemoryOutdatedLoad, +} + +/// Level of Miri specific diagnostics +enum DiagLevel { + Error, + Warning, + Note, +} + +/// Attempts to prune a stacktrace to omit the Rust runtime, and returns a bool indicating if any +/// frames were pruned. If the stacktrace does not have any local frames, we conclude that it must +/// be pointing to a problem in the Rust runtime itself, and do not prune it at all. +fn prune_stacktrace<'tcx>( + mut stacktrace: Vec>, + machine: &MiriMachine<'_, 'tcx>, +) -> (Vec>, bool) { + match machine.backtrace_style { + BacktraceStyle::Off => { + // Remove all frames marked with `caller_location` -- that attribute indicates we + // usually want to point at the caller, not them. + stacktrace.retain(|frame| !frame.instance.def.requires_caller_location(machine.tcx)); + // Retain one frame so that we can print a span for the error itself + stacktrace.truncate(1); + (stacktrace, false) + } + BacktraceStyle::Short => { + let original_len = stacktrace.len(); + // Only prune frames if there is at least one local frame. This check ensures that if + // we get a backtrace that never makes it to the user code because it has detected a + // bug in the Rust runtime, we don't prune away every frame. + let has_local_frame = stacktrace.iter().any(|frame| machine.is_local(frame)); + if has_local_frame { + // Remove all frames marked with `caller_location` -- that attribute indicates we + // usually want to point at the caller, not them. + stacktrace + .retain(|frame| !frame.instance.def.requires_caller_location(machine.tcx)); + + // This is part of the logic that `std` uses to select the relevant part of a + // backtrace. But here, we only look for __rust_begin_short_backtrace, not + // __rust_end_short_backtrace because the end symbol comes from a call to the default + // panic handler. + stacktrace = stacktrace + .into_iter() + .take_while(|frame| { + let def_id = frame.instance.def_id(); + let path = machine.tcx.def_path_str(def_id); + !path.contains("__rust_begin_short_backtrace") + }) + .collect::>(); + + // After we prune frames from the bottom, there are a few left that are part of the + // Rust runtime. So we remove frames until we get to a local symbol, which should be + // main or a test. + // This len check ensures that we don't somehow remove every frame, as doing so breaks + // the primary error message. + while stacktrace.len() > 1 + && stacktrace.last().map_or(false, |frame| !machine.is_local(frame)) + { + stacktrace.pop(); + } + } + let was_pruned = stacktrace.len() != original_len; + (stacktrace, was_pruned) + } + BacktraceStyle::Full => (stacktrace, false), + } +} + +/// Emit a custom diagnostic without going through the miri-engine machinery +pub fn report_error<'tcx, 'mir>( + ecx: &InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>, + e: InterpErrorInfo<'tcx>, +) -> Option { + use InterpError::*; + + let mut msg = vec![]; + + let (title, helps) = match &e.kind() { + MachineStop(info) => { + let info = info.downcast_ref::().expect("invalid MachineStop payload"); + use TerminationInfo::*; + let title = match info { + Exit(code) => return Some(*code), + Abort(_) => Some("abnormal termination"), + UnsupportedInIsolation(_) | Int2PtrWithStrictProvenance => + Some("unsupported operation"), + StackedBorrowsUb { .. } => Some("Undefined Behavior"), + Deadlock => Some("deadlock"), + MultipleSymbolDefinitions { .. } | SymbolShimClashing { .. } => None, + }; + #[rustfmt::skip] + let helps = match info { + UnsupportedInIsolation(_) => + vec![ + (None, format!("pass the flag `-Zmiri-disable-isolation` to disable isolation;")), + (None, format!("or pass `-Zmiri-isolation-error=warn` to configure Miri to return an error code from isolated operations (if supported for that operation) and continue with a warning")), + ], + StackedBorrowsUb { help, history, .. } => { + let url = "https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md"; + msg.extend(help.clone()); + let mut helps = vec![ + (None, format!("this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental")), + (None, format!("see {url} for further information")), + ]; + if let Some(TagHistory {created, invalidated, protected}) = history.clone() { + helps.push((Some(created.1), created.0)); + if let Some((msg, span)) = invalidated { + helps.push((Some(span), msg)); + } + if let Some((protector_msg, protector_span)) = protected { + helps.push((Some(protector_span), protector_msg)); + } + } + helps + } + MultipleSymbolDefinitions { first, first_crate, second, second_crate, .. } => + vec![ + (Some(*first), format!("it's first defined here, in crate `{first_crate}`")), + (Some(*second), format!("then it's defined here again, in crate `{second_crate}`")), + ], + SymbolShimClashing { link_name, span } => + vec![(Some(*span), format!("the `{link_name}` symbol is defined here"))], + Int2PtrWithStrictProvenance => + vec![(None, format!("use Strict Provenance APIs (https://doc.rust-lang.org/nightly/std/ptr/index.html#strict-provenance, https://crates.io/crates/sptr) instead"))], + _ => vec![], + }; + (title, helps) + } + _ => { + #[rustfmt::skip] + let title = match e.kind() { + Unsupported(_) => + "unsupported operation", + UndefinedBehavior(_) => + "Undefined Behavior", + ResourceExhaustion(_) => + "resource exhaustion", + InvalidProgram( + InvalidProgramInfo::AlreadyReported(_) | + InvalidProgramInfo::Layout(..) | + InvalidProgramInfo::ReferencedConstant + ) => + "post-monomorphization error", + kind => + bug!("This error should be impossible in Miri: {kind:?}"), + }; + #[rustfmt::skip] + let helps = match e.kind() { + Unsupported( + UnsupportedOpInfo::ThreadLocalStatic(_) | + UnsupportedOpInfo::ReadExternStatic(_) | + UnsupportedOpInfo::PartialPointerOverwrite(_) | // we make memory uninit instead + UnsupportedOpInfo::ReadPointerAsBytes + ) => + panic!("Error should never be raised by Miri: {kind:?}", kind = e.kind()), + Unsupported( + UnsupportedOpInfo::Unsupported(_) | + UnsupportedOpInfo::PartialPointerCopy(_) + ) => + vec![(None, format!("this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support"))], + UndefinedBehavior(UndefinedBehaviorInfo::AlignmentCheckFailed { .. }) + if ecx.machine.check_alignment == AlignmentCheck::Symbolic + => + vec![ + (None, format!("this usually indicates that your program performed an invalid operation and caused Undefined Behavior")), + (None, format!("but due to `-Zmiri-symbolic-alignment-check`, alignment errors can also be false positives")), + ], + UndefinedBehavior(_) => + vec![ + (None, format!("this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior")), + (None, format!("see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information")), + ], + InvalidProgram(_) | ResourceExhaustion(_) | MachineStop(_) => + vec![], + }; + (Some(title), helps) + } + }; + + let stacktrace = ecx.generate_stacktrace(); + let (stacktrace, was_pruned) = prune_stacktrace(stacktrace, &ecx.machine); + e.print_backtrace(); + msg.insert(0, e.to_string()); + report_msg( + DiagLevel::Error, + &if let Some(title) = title { format!("{}: {}", title, msg[0]) } else { msg[0].clone() }, + msg, + vec![], + helps, + &stacktrace, + &ecx.machine, + ); + + // Include a note like `std` does when we omit frames from a backtrace + if was_pruned { + ecx.tcx.sess.diagnostic().note_without_error( + "some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace", + ); + } + + // Debug-dump all locals. + for (i, frame) in ecx.active_thread_stack().iter().enumerate() { + trace!("-------------------"); + trace!("Frame {}", i); + trace!(" return: {:?}", *frame.return_place); + for (i, local) in frame.locals.iter().enumerate() { + trace!(" local {}: {:?}", i, local.value); + } + } + + // Extra output to help debug specific issues. + match e.kind() { + UndefinedBehavior(UndefinedBehaviorInfo::InvalidUninitBytes(Some((alloc_id, access)))) => { + eprintln!( + "Uninitialized memory occurred at {alloc_id:?}{range:?}, in this allocation:", + range = access.uninit, + ); + eprintln!("{:?}", ecx.dump_alloc(*alloc_id)); + } + _ => {} + } + + None +} + +/// Report an error or note (depending on the `error` argument) with the given stacktrace. +/// Also emits a full stacktrace of the interpreter stack. +/// We want to present a multi-line span message for some errors. Diagnostics do not support this +/// directly, so we pass the lines as a `Vec` and display each line after the first with an +/// additional `span_label` or `note` call. +fn report_msg<'tcx>( + diag_level: DiagLevel, + title: &str, + span_msg: Vec, + notes: Vec<(Option, String)>, + helps: Vec<(Option, String)>, + stacktrace: &[FrameInfo<'tcx>], + machine: &MiriMachine<'_, 'tcx>, +) { + let span = stacktrace.first().map_or(DUMMY_SP, |fi| fi.span); + let sess = machine.tcx.sess; + let mut err = match diag_level { + DiagLevel::Error => sess.struct_span_err(span, title).forget_guarantee(), + DiagLevel::Warning => sess.struct_span_warn(span, title), + DiagLevel::Note => sess.diagnostic().span_note_diag(span, title), + }; + + // Show main message. + if span != DUMMY_SP { + for line in span_msg { + err.span_label(span, line); + } + } else { + // Make sure we show the message even when it is a dummy span. + for line in span_msg { + err.note(&line); + } + err.note("(no span available)"); + } + + // Show note and help messages. + for (span_data, note) in ¬es { + if let Some(span_data) = span_data { + err.span_note(span_data.span(), note); + } else { + err.note(note); + } + } + for (span_data, help) in &helps { + if let Some(span_data) = span_data { + err.span_help(span_data.span(), help); + } else { + err.help(help); + } + } + if notes.len() + helps.len() > 0 { + // Add visual separator before backtrace. + err.note("BACKTRACE:"); + } + // Add backtrace + for (idx, frame_info) in stacktrace.iter().enumerate() { + let is_local = machine.is_local(frame_info); + // No span for non-local frames and the first frame (which is the error site). + if is_local && idx > 0 { + err.span_note(frame_info.span, &frame_info.to_string()); + } else { + err.note(&frame_info.to_string()); + } + } + + err.emit(); +} + +impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> { + pub fn emit_diagnostic(&self, e: NonHaltingDiagnostic) { + use NonHaltingDiagnostic::*; + + let stacktrace = + MiriInterpCx::generate_stacktrace_from_stack(self.threads.active_thread_stack()); + let (stacktrace, _was_pruned) = prune_stacktrace(stacktrace, self); + + let (title, diag_level) = match e { + RejectedIsolatedOp(_) => ("operation rejected by isolation", DiagLevel::Warning), + Int2Ptr { .. } => ("integer-to-pointer cast", DiagLevel::Warning), + CreatedPointerTag(..) + | PoppedPointerTag(..) + | CreatedCallId(..) + | CreatedAlloc(..) + | FreedAlloc(..) + | ProgressReport { .. } + | WeakMemoryOutdatedLoad => ("tracking was triggered", DiagLevel::Note), + }; + + let msg = match e { + CreatedPointerTag(tag, None) => format!("created tag {tag:?}"), + CreatedPointerTag(tag, Some((alloc_id, range))) => + format!("created tag {tag:?} at {alloc_id:?}{range:?}"), + PoppedPointerTag(item, tag) => + match tag { + None => format!("popped tracked tag for item {item:?} due to deallocation",), + Some((tag, access)) => { + format!( + "popped tracked tag for item {item:?} due to {access:?} access for {tag:?}", + ) + } + }, + CreatedCallId(id) => format!("function call with id {id}"), + CreatedAlloc(AllocId(id), size, align, kind) => + format!( + "created {kind} allocation of {size} bytes (alignment {align} bytes) with id {id}", + size = size.bytes(), + align = align.bytes(), + ), + FreedAlloc(AllocId(id)) => format!("freed allocation with id {id}"), + RejectedIsolatedOp(ref op) => + format!("{op} was made to return an error due to isolation"), + ProgressReport { .. } => + format!("progress report: current operation being executed is here"), + Int2Ptr { .. } => format!("integer-to-pointer cast"), + WeakMemoryOutdatedLoad => + format!("weak memory emulation: outdated value returned from load"), + }; + + let notes = match e { + ProgressReport { block_count } => { + // It is important that each progress report is slightly different, since + // identical diagnostics are being deduplicated. + vec![(None, format!("so far, {block_count} basic blocks have been executed"))] + } + _ => vec![], + }; + + let helps = match e { + Int2Ptr { details: true } => + vec![ + ( + None, + format!( + "This program is using integer-to-pointer casts or (equivalently) `ptr::from_exposed_addr`," + ), + ), + ( + None, + format!("which means that Miri might miss pointer bugs in this program."), + ), + ( + None, + format!( + "See https://doc.rust-lang.org/nightly/std/ptr/fn.from_exposed_addr.html for more details on that operation." + ), + ), + ( + None, + format!( + "To ensure that Miri does not miss bugs in your program, use Strict Provenance APIs (https://doc.rust-lang.org/nightly/std/ptr/index.html#strict-provenance, https://crates.io/crates/sptr) instead." + ), + ), + ( + None, + format!( + "You can then pass the `-Zmiri-strict-provenance` flag to Miri, to ensure you are not relying on `from_exposed_addr` semantics." + ), + ), + ( + None, + format!( + "Alternatively, the `-Zmiri-permissive-provenance` flag disables this warning." + ), + ), + ], + _ => vec![], + }; + + report_msg(diag_level, title, vec![msg], notes, helps, &stacktrace, self); + } +} + +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + fn emit_diagnostic(&self, e: NonHaltingDiagnostic) { + let this = self.eval_context_ref(); + this.machine.emit_diagnostic(e); + } + + /// We had a panic in Miri itself, try to print something useful. + fn handle_ice(&self) { + eprintln!(); + eprintln!( + "Miri caused an ICE during evaluation. Here's the interpreter backtrace at the time of the panic:" + ); + let this = self.eval_context_ref(); + let stacktrace = this.generate_stacktrace(); + report_msg( + DiagLevel::Note, + "the place in the program where the ICE was triggered", + vec![], + vec![], + vec![], + &stacktrace, + &this.machine, + ); + } +} diff --git a/src/tools/miri/src/eval.rs b/src/tools/miri/src/eval.rs new file mode 100644 index 0000000000000..91a2ac13b1b19 --- /dev/null +++ b/src/tools/miri/src/eval.rs @@ -0,0 +1,514 @@ +//! Main evaluator loop and setting up the initial stack frame. + +use std::ffi::{OsStr, OsString}; +use std::iter; +use std::panic::{self, AssertUnwindSafe}; +use std::path::PathBuf; +use std::thread; + +use log::info; + +use rustc_data_structures::fx::FxHashSet; +use rustc_hir::def_id::DefId; +use rustc_middle::ty::{ + self, + layout::{LayoutCx, LayoutOf}, + TyCtxt, +}; +use rustc_target::spec::abi::Abi; + +use rustc_session::config::EntryFnType; + +use crate::*; + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum AlignmentCheck { + /// Do not check alignment. + None, + /// Check alignment "symbolically", i.e., using only the requested alignment for an allocation and not its real base address. + Symbolic, + /// Check alignment on the actual physical integer address. + Int, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum RejectOpWith { + /// Isolated op is rejected with an abort of the machine. + Abort, + + /// If not Abort, miri returns an error for an isolated op. + /// Following options determine if user should be warned about such error. + /// Do not print warning about rejected isolated op. + NoWarning, + + /// Print a warning about rejected isolated op, with backtrace. + Warning, + + /// Print a warning about rejected isolated op, without backtrace. + WarningWithoutBacktrace, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum IsolatedOp { + /// Reject an op requiring communication with the host. By + /// default, miri rejects the op with an abort. If not, it returns + /// an error code, and prints a warning about it. Warning levels + /// are controlled by `RejectOpWith` enum. + Reject(RejectOpWith), + + /// Execute op requiring communication with the host, i.e. disable isolation. + Allow, +} + +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum BacktraceStyle { + /// Prints a terser backtrace which ideally only contains relevant information. + Short, + /// Prints a backtrace with all possible information. + Full, + /// Prints only the frame that the error occurs in. + Off, +} + +/// Configuration needed to spawn a Miri instance. +#[derive(Clone)] +pub struct MiriConfig { + /// The host environment snapshot to use as basis for what is provided to the interpreted program. + /// (This is still subject to isolation as well as `forwarded_env_vars`.) + pub env: Vec<(OsString, OsString)>, + /// Determine if validity checking is enabled. + pub validate: bool, + /// Determines if Stacked Borrows is enabled. + pub stacked_borrows: bool, + /// Controls alignment checking. + pub check_alignment: AlignmentCheck, + /// Controls function [ABI](Abi) checking. + pub check_abi: bool, + /// Action for an op requiring communication with the host. + pub isolated_op: IsolatedOp, + /// Determines if memory leaks should be ignored. + pub ignore_leaks: bool, + /// Environment variables that should always be forwarded from the host. + pub forwarded_env_vars: Vec, + /// Command-line arguments passed to the interpreted program. + pub args: Vec, + /// The seed to use when non-determinism or randomness are required (e.g. ptr-to-int cast, `getrandom()`). + pub seed: Option, + /// The stacked borrows pointer ids to report about + pub tracked_pointer_tags: FxHashSet, + /// The stacked borrows call IDs to report about + pub tracked_call_ids: FxHashSet, + /// The allocation ids to report about. + pub tracked_alloc_ids: FxHashSet, + /// Determine if data race detection should be enabled + pub data_race_detector: bool, + /// Determine if weak memory emulation should be enabled. Requires data race detection to be enabled + pub weak_memory_emulation: bool, + /// Track when an outdated (weak memory) load happens. + pub track_outdated_loads: bool, + /// Rate of spurious failures for compare_exchange_weak atomic operations, + /// between 0.0 and 1.0, defaulting to 0.8 (80% chance of failure). + pub cmpxchg_weak_failure_rate: f64, + /// If `Some`, enable the `measureme` profiler, writing results to a file + /// with the specified prefix. + pub measureme_out: Option, + /// Panic when unsupported functionality is encountered. + pub panic_on_unsupported: bool, + /// Which style to use for printing backtraces. + pub backtrace_style: BacktraceStyle, + /// Which provenance to use for int2ptr casts + pub provenance_mode: ProvenanceMode, + /// Whether to ignore any output by the program. This is helpful when debugging miri + /// as its messages don't get intermingled with the program messages. + pub mute_stdout_stderr: bool, + /// The probability of the active thread being preempted at the end of each basic block. + pub preemption_rate: f64, + /// Report the current instruction being executed every N basic blocks. + pub report_progress: Option, + /// Whether Stacked Borrows retagging should recurse into fields of datatypes. + pub retag_fields: bool, + /// The location of a shared object file to load when calling external functions + /// FIXME! consider allowing users to specify paths to multiple SO files, or to a directory + pub external_so_file: Option, + /// Run a garbage collector for SbTags every N basic blocks. + pub gc_interval: u32, +} + +impl Default for MiriConfig { + fn default() -> MiriConfig { + MiriConfig { + env: vec![], + validate: true, + stacked_borrows: true, + check_alignment: AlignmentCheck::Int, + check_abi: true, + isolated_op: IsolatedOp::Reject(RejectOpWith::Abort), + ignore_leaks: false, + forwarded_env_vars: vec![], + args: vec![], + seed: None, + tracked_pointer_tags: FxHashSet::default(), + tracked_call_ids: FxHashSet::default(), + tracked_alloc_ids: FxHashSet::default(), + data_race_detector: true, + weak_memory_emulation: true, + track_outdated_loads: false, + cmpxchg_weak_failure_rate: 0.8, // 80% + measureme_out: None, + panic_on_unsupported: false, + backtrace_style: BacktraceStyle::Short, + provenance_mode: ProvenanceMode::Default, + mute_stdout_stderr: false, + preemption_rate: 0.01, // 1% + report_progress: None, + retag_fields: false, + external_so_file: None, + gc_interval: 10_000, + } + } +} + +/// Returns a freshly created `InterpCx`, along with an `MPlaceTy` representing +/// the location where the return value of the `start` function will be +/// written to. +/// Public because this is also used by `priroda`. +pub fn create_ecx<'mir, 'tcx: 'mir>( + tcx: TyCtxt<'tcx>, + entry_id: DefId, + entry_type: EntryFnType, + config: &MiriConfig, +) -> InterpResult<'tcx, (InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>, MPlaceTy<'tcx, Provenance>)> +{ + let param_env = ty::ParamEnv::reveal_all(); + let layout_cx = LayoutCx { tcx, param_env }; + let mut ecx = InterpCx::new( + tcx, + rustc_span::source_map::DUMMY_SP, + param_env, + MiriMachine::new(config, layout_cx), + ); + + // Some parts of initialization require a full `InterpCx`. + MiriMachine::late_init(&mut ecx, config)?; + + // Make sure we have MIR. We check MIR for some stable monomorphic function in libcore. + let sentinel = ecx.try_resolve_path(&["core", "ascii", "escape_default"]); + if !matches!(sentinel, Some(s) if tcx.is_mir_available(s.def.def_id())) { + tcx.sess.fatal( + "the current sysroot was built without `-Zalways-encode-mir`, or libcore seems missing. \ + Use `cargo miri setup` to prepare a sysroot that is suitable for Miri." + ); + } + + // Setup first stack frame. + let entry_instance = ty::Instance::mono(tcx, entry_id); + + // First argument is constructed later, because it's skipped if the entry function uses #[start]. + + // Second argument (argc): length of `config.args`. + let argc = Scalar::from_machine_usize(u64::try_from(config.args.len()).unwrap(), &ecx); + // Third argument (`argv`): created from `config.args`. + let argv = { + // Put each argument in memory, collect pointers. + let mut argvs = Vec::>::new(); + for arg in config.args.iter() { + // Make space for `0` terminator. + let size = u64::try_from(arg.len()).unwrap().checked_add(1).unwrap(); + let arg_type = tcx.mk_array(tcx.types.u8, size); + let arg_place = + ecx.allocate(ecx.layout_of(arg_type)?, MiriMemoryKind::Machine.into())?; + ecx.write_os_str_to_c_str(OsStr::new(arg), arg_place.ptr, size)?; + ecx.mark_immutable(&arg_place); + argvs.push(arg_place.to_ref(&ecx)); + } + // Make an array with all these pointers, in the Miri memory. + let argvs_layout = ecx.layout_of( + tcx.mk_array(tcx.mk_imm_ptr(tcx.types.u8), u64::try_from(argvs.len()).unwrap()), + )?; + let argvs_place = ecx.allocate(argvs_layout, MiriMemoryKind::Machine.into())?; + for (idx, arg) in argvs.into_iter().enumerate() { + let place = ecx.mplace_field(&argvs_place, idx)?; + ecx.write_immediate(arg, &place.into())?; + } + ecx.mark_immutable(&argvs_place); + // A pointer to that place is the 3rd argument for main. + let argv = argvs_place.to_ref(&ecx); + // Store `argc` and `argv` for macOS `_NSGetArg{c,v}`. + { + let argc_place = + ecx.allocate(ecx.machine.layouts.isize, MiriMemoryKind::Machine.into())?; + ecx.write_scalar(argc, &argc_place.into())?; + ecx.mark_immutable(&argc_place); + ecx.machine.argc = Some(*argc_place); + + let argv_place = ecx.allocate( + ecx.layout_of(tcx.mk_imm_ptr(tcx.types.unit))?, + MiriMemoryKind::Machine.into(), + )?; + ecx.write_immediate(argv, &argv_place.into())?; + ecx.mark_immutable(&argv_place); + ecx.machine.argv = Some(*argv_place); + } + // Store command line as UTF-16 for Windows `GetCommandLineW`. + { + // Construct a command string with all the arguments. + let cmd_utf16: Vec = args_to_utf16_command_string(config.args.iter()); + + let cmd_type = tcx.mk_array(tcx.types.u16, u64::try_from(cmd_utf16.len()).unwrap()); + let cmd_place = + ecx.allocate(ecx.layout_of(cmd_type)?, MiriMemoryKind::Machine.into())?; + ecx.machine.cmd_line = Some(*cmd_place); + // Store the UTF-16 string. We just allocated so we know the bounds are fine. + for (idx, &c) in cmd_utf16.iter().enumerate() { + let place = ecx.mplace_field(&cmd_place, idx)?; + ecx.write_scalar(Scalar::from_u16(c), &place.into())?; + } + ecx.mark_immutable(&cmd_place); + } + argv + }; + + // Return place (in static memory so that it does not count as leak). + let ret_place = ecx.allocate(ecx.machine.layouts.isize, MiriMemoryKind::Machine.into())?; + // Call start function. + + match entry_type { + EntryFnType::Main { .. } => { + let start_id = tcx.lang_items().start_fn().unwrap(); + let main_ret_ty = tcx.fn_sig(entry_id).output(); + let main_ret_ty = main_ret_ty.no_bound_vars().unwrap(); + let start_instance = ty::Instance::resolve( + tcx, + ty::ParamEnv::reveal_all(), + start_id, + tcx.mk_substs(::std::iter::once(ty::subst::GenericArg::from(main_ret_ty))), + ) + .unwrap() + .unwrap(); + + let main_ptr = ecx.create_fn_alloc_ptr(FnVal::Instance(entry_instance)); + + // Inlining of `DEFAULT` from + // https://github.com/rust-lang/rust/blob/master/compiler/rustc_session/src/config/sigpipe.rs. + // Alaways using DEFAULT is okay since we don't support signals in Miri anyway. + let sigpipe = 2; + + ecx.call_function( + start_instance, + Abi::Rust, + &[ + Scalar::from_pointer(main_ptr, &ecx).into(), + argc.into(), + argv, + Scalar::from_u8(sigpipe).into(), + ], + Some(&ret_place.into()), + StackPopCleanup::Root { cleanup: true }, + )?; + } + EntryFnType::Start => { + ecx.call_function( + entry_instance, + Abi::Rust, + &[argc.into(), argv], + Some(&ret_place.into()), + StackPopCleanup::Root { cleanup: true }, + )?; + } + } + + Ok((ecx, ret_place)) +} + +/// Evaluates the entry function specified by `entry_id`. +/// Returns `Some(return_code)` if program executed completed. +/// Returns `None` if an evaluation error occurred. +#[allow(clippy::needless_lifetimes)] +pub fn eval_entry<'tcx>( + tcx: TyCtxt<'tcx>, + entry_id: DefId, + entry_type: EntryFnType, + config: MiriConfig, +) -> Option { + // Copy setting before we move `config`. + let ignore_leaks = config.ignore_leaks; + + let (mut ecx, ret_place) = match create_ecx(tcx, entry_id, entry_type, &config) { + Ok(v) => v, + Err(err) => { + err.print_backtrace(); + panic!("Miri initialization error: {}", err.kind()) + } + }; + + // Perform the main execution. + let res: thread::Result> = panic::catch_unwind(AssertUnwindSafe(|| { + // Main loop. + loop { + match ecx.schedule()? { + SchedulingAction::ExecuteStep => { + assert!(ecx.step()?, "a terminated thread was scheduled for execution"); + } + SchedulingAction::ExecuteTimeoutCallback => { + ecx.run_timeout_callback()?; + } + SchedulingAction::ExecuteDtors => { + // This will either enable the thread again (so we go back + // to `ExecuteStep`), or determine that this thread is done + // for good. + ecx.schedule_next_tls_dtor_for_active_thread()?; + } + SchedulingAction::Stop => { + break; + } + } + } + let return_code = ecx.read_scalar(&ret_place.into())?.to_machine_isize(&ecx)?; + Ok(return_code) + })); + let res = res.unwrap_or_else(|panic_payload| { + ecx.handle_ice(); + panic::resume_unwind(panic_payload) + }); + + // Machine cleanup. Only do this if all threads have terminated; threads that are still running + // might cause Stacked Borrows errors (https://github.com/rust-lang/miri/issues/2396). + if ecx.have_all_terminated() { + // Even if all threads have terminated, we have to beware of data races since some threads + // might not have joined the main thread (https://github.com/rust-lang/miri/issues/2020, + // https://github.com/rust-lang/miri/issues/2508). + ecx.allow_data_races_all_threads_done(); + EnvVars::cleanup(&mut ecx).expect("error during env var cleanup"); + } + + // Process the result. + match res { + Ok(return_code) => { + if !ignore_leaks { + // Check for thread leaks. + if !ecx.have_all_terminated() { + tcx.sess.err( + "the main thread terminated without waiting for all remaining threads", + ); + tcx.sess.note_without_error("pass `-Zmiri-ignore-leaks` to disable this check"); + return None; + } + // Check for memory leaks. + info!("Additonal static roots: {:?}", ecx.machine.static_roots); + let leaks = ecx.leak_report(&ecx.machine.static_roots); + if leaks != 0 { + tcx.sess.err("the evaluated program leaked memory"); + tcx.sess.note_without_error("pass `-Zmiri-ignore-leaks` to disable this check"); + // Ignore the provided return code - let the reported error + // determine the return code. + return None; + } + } + Some(return_code) + } + Err(e) => report_error(&ecx, e), + } +} + +/// Turns an array of arguments into a Windows command line string. +/// +/// The string will be UTF-16 encoded and NUL terminated. +/// +/// Panics if the zeroth argument contains the `"` character because doublequotes +/// in `argv[0]` cannot be encoded using the standard command line parsing rules. +/// +/// Further reading: +/// * [Parsing C++ command-line arguments](https://docs.microsoft.com/en-us/cpp/cpp/main-function-command-line-args?view=msvc-160#parsing-c-command-line-arguments) +/// * [The C/C++ Parameter Parsing Rules](https://daviddeley.com/autohotkey/parameters/parameters.htm#WINCRULES) +fn args_to_utf16_command_string(mut args: I) -> Vec +where + I: Iterator, + T: AsRef, +{ + // Parse argv[0]. Slashes aren't escaped. Literal double quotes are not allowed. + let mut cmd = { + let arg0 = if let Some(arg0) = args.next() { + arg0 + } else { + return vec![0]; + }; + let arg0 = arg0.as_ref(); + if arg0.contains('"') { + panic!("argv[0] cannot contain a doublequote (\") character"); + } else { + // Always surround argv[0] with quotes. + let mut s = String::new(); + s.push('"'); + s.push_str(arg0); + s.push('"'); + s + } + }; + + // Build the other arguments. + for arg in args { + let arg = arg.as_ref(); + cmd.push(' '); + if arg.is_empty() { + cmd.push_str("\"\""); + } else if !arg.bytes().any(|c| matches!(c, b'"' | b'\t' | b' ')) { + // No quote, tab, or space -- no escaping required. + cmd.push_str(arg); + } else { + // Spaces and tabs are escaped by surrounding them in quotes. + // Quotes are themselves escaped by using backslashes when in a + // quoted block. + // Backslashes only need to be escaped when one or more are directly + // followed by a quote. Otherwise they are taken literally. + + cmd.push('"'); + let mut chars = arg.chars().peekable(); + loop { + let mut nslashes = 0; + while let Some(&'\\') = chars.peek() { + chars.next(); + nslashes += 1; + } + + match chars.next() { + Some('"') => { + cmd.extend(iter::repeat('\\').take(nslashes * 2 + 1)); + cmd.push('"'); + } + Some(c) => { + cmd.extend(iter::repeat('\\').take(nslashes)); + cmd.push(c); + } + None => { + cmd.extend(iter::repeat('\\').take(nslashes * 2)); + break; + } + } + } + cmd.push('"'); + } + } + + if cmd.contains('\0') { + panic!("interior null in command line arguments"); + } + cmd.encode_utf16().chain(iter::once(0)).collect() +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + #[should_panic(expected = "argv[0] cannot contain a doublequote (\") character")] + fn windows_argv0_panic_on_quote() { + args_to_utf16_command_string(["\""].iter()); + } + #[test] + fn windows_argv0_no_escape() { + // Ensure that a trailing backslash in argv[0] is not escaped. + let cmd = String::from_utf16_lossy(&args_to_utf16_command_string( + [r"C:\Program Files\", "arg1", "arg 2", "arg \" 3"].iter(), + )); + assert_eq!(cmd.trim_end_matches('\0'), r#""C:\Program Files\" arg1 "arg 2" "arg \" 3""#); + } +} diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs new file mode 100644 index 0000000000000..0f0bfa355bdc7 --- /dev/null +++ b/src/tools/miri/src/helpers.rs @@ -0,0 +1,1004 @@ +pub mod convert; + +use std::mem; +use std::num::NonZeroUsize; +use std::time::Duration; + +use log::trace; + +use rustc_hir::def_id::{DefId, CRATE_DEF_INDEX}; +use rustc_middle::mir; +use rustc_middle::ty::{ + self, + layout::{LayoutOf, TyAndLayout}, + List, TyCtxt, +}; +use rustc_span::{def_id::CrateNum, sym, Span, Symbol}; +use rustc_target::abi::{Align, FieldsShape, Size, Variants}; +use rustc_target::spec::abi::Abi; + +use rand::RngCore; + +use crate::*; + +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} + +// This mapping should match `decode_error_kind` in +// . +const UNIX_IO_ERROR_TABLE: &[(&str, std::io::ErrorKind)] = { + use std::io::ErrorKind::*; + &[ + ("E2BIG", ArgumentListTooLong), + ("EADDRINUSE", AddrInUse), + ("EADDRNOTAVAIL", AddrNotAvailable), + ("EBUSY", ResourceBusy), + ("ECONNABORTED", ConnectionAborted), + ("ECONNREFUSED", ConnectionRefused), + ("ECONNRESET", ConnectionReset), + ("EDEADLK", Deadlock), + ("EDQUOT", FilesystemQuotaExceeded), + ("EEXIST", AlreadyExists), + ("EFBIG", FileTooLarge), + ("EHOSTUNREACH", HostUnreachable), + ("EINTR", Interrupted), + ("EINVAL", InvalidInput), + ("EISDIR", IsADirectory), + ("ELOOP", FilesystemLoop), + ("ENOENT", NotFound), + ("ENOMEM", OutOfMemory), + ("ENOSPC", StorageFull), + ("ENOSYS", Unsupported), + ("EMLINK", TooManyLinks), + ("ENAMETOOLONG", InvalidFilename), + ("ENETDOWN", NetworkDown), + ("ENETUNREACH", NetworkUnreachable), + ("ENOTCONN", NotConnected), + ("ENOTDIR", NotADirectory), + ("ENOTEMPTY", DirectoryNotEmpty), + ("EPIPE", BrokenPipe), + ("EROFS", ReadOnlyFilesystem), + ("ESPIPE", NotSeekable), + ("ESTALE", StaleNetworkFileHandle), + ("ETIMEDOUT", TimedOut), + ("ETXTBSY", ExecutableFileBusy), + ("EXDEV", CrossesDevices), + // The following have two valid options. We have both for the forwards mapping; only the + // first one will be used for the backwards mapping. + ("EPERM", PermissionDenied), + ("EACCES", PermissionDenied), + ("EWOULDBLOCK", WouldBlock), + ("EAGAIN", WouldBlock), + ] +}; + +/// Gets an instance for a path. +fn try_resolve_did<'tcx>(tcx: TyCtxt<'tcx>, path: &[&str]) -> Option { + tcx.crates(()).iter().find(|&&krate| tcx.crate_name(krate).as_str() == path[0]).and_then( + |krate| { + let krate = DefId { krate: *krate, index: CRATE_DEF_INDEX }; + let mut items = tcx.module_children(krate); + let mut path_it = path.iter().skip(1).peekable(); + + while let Some(segment) = path_it.next() { + for item in mem::take(&mut items).iter() { + if item.ident.name.as_str() == *segment { + if path_it.peek().is_none() { + return Some(item.res.def_id()); + } + + items = tcx.module_children(item.res.def_id()); + break; + } + } + } + None + }, + ) +} + +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + /// Gets an instance for a path; fails gracefully if the path does not exist. + fn try_resolve_path(&self, path: &[&str]) -> Option> { + let did = try_resolve_did(self.eval_context_ref().tcx.tcx, path)?; + Some(ty::Instance::mono(self.eval_context_ref().tcx.tcx, did)) + } + + /// Gets an instance for a path. + fn resolve_path(&self, path: &[&str]) -> ty::Instance<'tcx> { + self.try_resolve_path(path) + .unwrap_or_else(|| panic!("failed to find required Rust item: {:?}", path)) + } + + /// Evaluates the scalar at the specified path. Returns Some(val) + /// if the path could be resolved, and None otherwise + fn eval_path_scalar(&self, path: &[&str]) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_ref(); + let instance = this.resolve_path(path); + let cid = GlobalId { instance, promoted: None }; + let const_val = this.eval_to_allocation(cid)?; + this.read_scalar(&const_val.into()) + } + + /// Helper function to get a `libc` constant as a `Scalar`. + fn eval_libc(&self, name: &str) -> InterpResult<'tcx, Scalar> { + self.eval_path_scalar(&["libc", name]) + } + + /// Helper function to get a `libc` constant as an `i32`. + fn eval_libc_i32(&self, name: &str) -> InterpResult<'tcx, i32> { + // TODO: Cache the result. + self.eval_libc(name)?.to_i32() + } + + /// Helper function to get a `windows` constant as a `Scalar`. + fn eval_windows(&self, module: &str, name: &str) -> InterpResult<'tcx, Scalar> { + self.eval_context_ref().eval_path_scalar(&["std", "sys", "windows", module, name]) + } + + /// Helper function to get a `windows` constant as a `u64`. + fn eval_windows_u64(&self, module: &str, name: &str) -> InterpResult<'tcx, u64> { + // TODO: Cache the result. + self.eval_windows(module, name)?.to_u64() + } + + /// Helper function to get the `TyAndLayout` of a `libc` type + fn libc_ty_layout(&self, name: &str) -> InterpResult<'tcx, TyAndLayout<'tcx>> { + let this = self.eval_context_ref(); + let ty = this.resolve_path(&["libc", name]).ty(*this.tcx, ty::ParamEnv::reveal_all()); + this.layout_of(ty) + } + + /// Helper function to get the `TyAndLayout` of a `windows` type + fn windows_ty_layout(&self, name: &str) -> InterpResult<'tcx, TyAndLayout<'tcx>> { + let this = self.eval_context_ref(); + let ty = this + .resolve_path(&["std", "sys", "windows", "c", name]) + .ty(*this.tcx, ty::ParamEnv::reveal_all()); + this.layout_of(ty) + } + + /// Project to the given *named* field of the mplace (which must be a struct or union type). + fn mplace_field_named( + &self, + mplace: &MPlaceTy<'tcx, Provenance>, + name: &str, + ) -> InterpResult<'tcx, MPlaceTy<'tcx, Provenance>> { + let this = self.eval_context_ref(); + let adt = mplace.layout.ty.ty_adt_def().unwrap(); + for (idx, field) in adt.non_enum_variant().fields.iter().enumerate() { + if field.name.as_str() == name { + return this.mplace_field(mplace, idx); + } + } + bug!("No field named {} in type {}", name, mplace.layout.ty); + } + + /// Write an int of the appropriate size to `dest`. The target type may be signed or unsigned, + /// we try to do the right thing anyway. `i128` can fit all integer types except for `u128` so + /// this method is fine for almost all integer types. + fn write_int( + &mut self, + i: impl Into, + dest: &PlaceTy<'tcx, Provenance>, + ) -> InterpResult<'tcx> { + assert!(dest.layout.abi.is_scalar(), "write_int on non-scalar type {}", dest.layout.ty); + let val = if dest.layout.abi.is_signed() { + Scalar::from_int(i, dest.layout.size) + } else { + Scalar::from_uint(u64::try_from(i.into()).unwrap(), dest.layout.size) + }; + self.eval_context_mut().write_scalar(val, dest) + } + + /// Write the first N fields of the given place. + fn write_int_fields( + &mut self, + values: &[i128], + dest: &MPlaceTy<'tcx, Provenance>, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + for (idx, &val) in values.iter().enumerate() { + let field = this.mplace_field(dest, idx)?; + this.write_int(val, &field.into())?; + } + Ok(()) + } + + /// Write the given fields of the given place. + fn write_int_fields_named( + &mut self, + values: &[(&str, i128)], + dest: &MPlaceTy<'tcx, Provenance>, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + for &(name, val) in values.iter() { + let field = this.mplace_field_named(dest, name)?; + this.write_int(val, &field.into())?; + } + Ok(()) + } + + /// Write a 0 of the appropriate size to `dest`. + fn write_null(&mut self, dest: &PlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> { + self.write_int(0, dest) + } + + /// Test if this pointer equals 0. + fn ptr_is_null(&self, ptr: Pointer>) -> InterpResult<'tcx, bool> { + Ok(ptr.addr().bytes() == 0) + } + + /// Get the `Place` for a local + fn local_place(&mut self, local: mir::Local) -> InterpResult<'tcx, PlaceTy<'tcx, Provenance>> { + let this = self.eval_context_mut(); + let place = mir::Place { local, projection: List::empty() }; + this.eval_place(place) + } + + /// Generate some random bytes, and write them to `dest`. + fn gen_random(&mut self, ptr: Pointer>, len: u64) -> InterpResult<'tcx> { + // Some programs pass in a null pointer and a length of 0 + // to their platform's random-generation function (e.g. getrandom()) + // on Linux. For compatibility with these programs, we don't perform + // any additional checks - it's okay if the pointer is invalid, + // since we wouldn't actually be writing to it. + if len == 0 { + return Ok(()); + } + let this = self.eval_context_mut(); + + let mut data = vec![0; usize::try_from(len).unwrap()]; + + if this.machine.communicate() { + // Fill the buffer using the host's rng. + getrandom::getrandom(&mut data) + .map_err(|err| err_unsup_format!("host getrandom failed: {}", err))?; + } else { + let rng = this.machine.rng.get_mut(); + rng.fill_bytes(&mut data); + } + + this.write_bytes_ptr(ptr, data.iter().copied()) + } + + /// Call a function: Push the stack frame and pass the arguments. + /// For now, arguments must be scalars (so that the caller does not have to know the layout). + /// + /// If you do not provie a return place, a dangling zero-sized place will be created + /// for your convenience. + fn call_function( + &mut self, + f: ty::Instance<'tcx>, + caller_abi: Abi, + args: &[Immediate], + dest: Option<&PlaceTy<'tcx, Provenance>>, + stack_pop: StackPopCleanup, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + let param_env = ty::ParamEnv::reveal_all(); // in Miri this is always the param_env we use... and this.param_env is private. + let callee_abi = f.ty(*this.tcx, param_env).fn_sig(*this.tcx).abi(); + if this.machine.enforce_abi && callee_abi != caller_abi { + throw_ub_format!( + "calling a function with ABI {} using caller ABI {}", + callee_abi.name(), + caller_abi.name() + ) + } + + // Push frame. + let mir = this.load_mir(f.def, None)?; + let dest = match dest { + Some(dest) => dest.clone(), + None => MPlaceTy::fake_alloc_zst(this.layout_of(mir.return_ty())?).into(), + }; + this.push_stack_frame(f, mir, &dest, stack_pop)?; + + // Initialize arguments. + let mut callee_args = this.frame().body.args_iter(); + for arg in args { + let callee_arg = this.local_place( + callee_args + .next() + .ok_or_else(|| err_ub_format!("callee has fewer arguments than expected"))?, + )?; + this.write_immediate(*arg, &callee_arg)?; + } + if callee_args.next().is_some() { + throw_ub_format!("callee has more arguments than expected"); + } + + Ok(()) + } + + /// Visits the memory covered by `place`, sensitive to freezing: the 2nd parameter + /// of `action` will be true if this is frozen, false if this is in an `UnsafeCell`. + /// The range is relative to `place`. + fn visit_freeze_sensitive( + &self, + place: &MPlaceTy<'tcx, Provenance>, + size: Size, + mut action: impl FnMut(AllocRange, bool) -> InterpResult<'tcx>, + ) -> InterpResult<'tcx> { + let this = self.eval_context_ref(); + trace!("visit_frozen(place={:?}, size={:?})", *place, size); + debug_assert_eq!( + size, + this.size_and_align_of_mplace(place)? + .map(|(size, _)| size) + .unwrap_or_else(|| place.layout.size) + ); + // Store how far we proceeded into the place so far. Everything to the left of + // this offset has already been handled, in the sense that the frozen parts + // have had `action` called on them. + let start_addr = place.ptr.addr(); + let mut cur_addr = start_addr; + // Called when we detected an `UnsafeCell` at the given offset and size. + // Calls `action` and advances `cur_ptr`. + let mut unsafe_cell_action = |unsafe_cell_ptr: &Pointer>, + unsafe_cell_size: Size| { + // We assume that we are given the fields in increasing offset order, + // and nothing else changes. + let unsafe_cell_addr = unsafe_cell_ptr.addr(); + assert!(unsafe_cell_addr >= cur_addr); + let frozen_size = unsafe_cell_addr - cur_addr; + // Everything between the cur_ptr and this `UnsafeCell` is frozen. + if frozen_size != Size::ZERO { + action(alloc_range(cur_addr - start_addr, frozen_size), /*frozen*/ true)?; + } + cur_addr += frozen_size; + // This `UnsafeCell` is NOT frozen. + if unsafe_cell_size != Size::ZERO { + action( + alloc_range(cur_addr - start_addr, unsafe_cell_size), + /*frozen*/ false, + )?; + } + cur_addr += unsafe_cell_size; + // Done + Ok(()) + }; + // Run a visitor + { + let mut visitor = UnsafeCellVisitor { + ecx: this, + unsafe_cell_action: |place| { + trace!("unsafe_cell_action on {:?}", place.ptr); + // We need a size to go on. + let unsafe_cell_size = this + .size_and_align_of_mplace(place)? + .map(|(size, _)| size) + // for extern types, just cover what we can + .unwrap_or_else(|| place.layout.size); + // Now handle this `UnsafeCell`, unless it is empty. + if unsafe_cell_size != Size::ZERO { + unsafe_cell_action(&place.ptr, unsafe_cell_size) + } else { + Ok(()) + } + }, + }; + visitor.visit_value(place)?; + } + // The part between the end_ptr and the end of the place is also frozen. + // So pretend there is a 0-sized `UnsafeCell` at the end. + unsafe_cell_action(&place.ptr.offset(size, this)?, Size::ZERO)?; + // Done! + return Ok(()); + + /// Visiting the memory covered by a `MemPlace`, being aware of + /// whether we are inside an `UnsafeCell` or not. + struct UnsafeCellVisitor<'ecx, 'mir, 'tcx, F> + where + F: FnMut(&MPlaceTy<'tcx, Provenance>) -> InterpResult<'tcx>, + { + ecx: &'ecx MiriInterpCx<'mir, 'tcx>, + unsafe_cell_action: F, + } + + impl<'ecx, 'mir, 'tcx: 'mir, F> ValueVisitor<'mir, 'tcx, MiriMachine<'mir, 'tcx>> + for UnsafeCellVisitor<'ecx, 'mir, 'tcx, F> + where + F: FnMut(&MPlaceTy<'tcx, Provenance>) -> InterpResult<'tcx>, + { + type V = MPlaceTy<'tcx, Provenance>; + + #[inline(always)] + fn ecx(&self) -> &MiriInterpCx<'mir, 'tcx> { + self.ecx + } + + // Hook to detect `UnsafeCell`. + fn visit_value(&mut self, v: &MPlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> { + trace!("UnsafeCellVisitor: {:?} {:?}", *v, v.layout.ty); + let is_unsafe_cell = match v.layout.ty.kind() { + ty::Adt(adt, _) => + Some(adt.did()) == self.ecx.tcx.lang_items().unsafe_cell_type(), + _ => false, + }; + if is_unsafe_cell { + // We do not have to recurse further, this is an `UnsafeCell`. + (self.unsafe_cell_action)(v) + } else if self.ecx.type_is_freeze(v.layout.ty) { + // This is `Freeze`, there cannot be an `UnsafeCell` + Ok(()) + } else if matches!(v.layout.fields, FieldsShape::Union(..)) { + // A (non-frozen) union. We fall back to whatever the type says. + (self.unsafe_cell_action)(v) + } else { + // We want to not actually read from memory for this visit. So, before + // walking this value, we have to make sure it is not a + // `Variants::Multiple`. + match v.layout.variants { + Variants::Multiple { .. } => { + // A multi-variant enum, or generator, or so. + // Treat this like a union: without reading from memory, + // we cannot determine the variant we are in. Reading from + // memory would be subject to Stacked Borrows rules, leading + // to all sorts of "funny" recursion. + // We only end up here if the type is *not* freeze, so we just call the + // `UnsafeCell` action. + (self.unsafe_cell_action)(v) + } + Variants::Single { .. } => { + // Proceed further, try to find where exactly that `UnsafeCell` + // is hiding. + self.walk_value(v) + } + } + } + } + + // Make sure we visit aggregrates in increasing offset order. + fn visit_aggregate( + &mut self, + place: &MPlaceTy<'tcx, Provenance>, + fields: impl Iterator>>, + ) -> InterpResult<'tcx> { + match place.layout.fields { + FieldsShape::Array { .. } => { + // For the array layout, we know the iterator will yield sorted elements so + // we can avoid the allocation. + self.walk_aggregate(place, fields) + } + FieldsShape::Arbitrary { .. } => { + // Gather the subplaces and sort them before visiting. + let mut places = fields + .collect::>>>()?; + // we just compare offsets, the abs. value never matters + places.sort_by_key(|place| place.ptr.addr()); + self.walk_aggregate(place, places.into_iter().map(Ok)) + } + FieldsShape::Union { .. } | FieldsShape::Primitive => { + // Uh, what? + bug!("unions/primitives are not aggregates we should ever visit") + } + } + } + + fn visit_union( + &mut self, + _v: &MPlaceTy<'tcx, Provenance>, + _fields: NonZeroUsize, + ) -> InterpResult<'tcx> { + bug!("we should have already handled unions in `visit_value`") + } + } + } + + /// Helper function used inside the shims of foreign functions to check that isolation is + /// disabled. It returns an error using the `name` of the foreign function if this is not the + /// case. + fn check_no_isolation(&self, name: &str) -> InterpResult<'tcx> { + if !self.eval_context_ref().machine.communicate() { + self.reject_in_isolation(name, RejectOpWith::Abort)?; + } + Ok(()) + } + + /// Helper function used inside the shims of foreign functions which reject the op + /// when isolation is enabled. It is used to print a warning/backtrace about the rejection. + fn reject_in_isolation(&self, op_name: &str, reject_with: RejectOpWith) -> InterpResult<'tcx> { + let this = self.eval_context_ref(); + match reject_with { + RejectOpWith::Abort => isolation_abort_error(op_name), + RejectOpWith::WarningWithoutBacktrace => { + this.tcx + .sess + .warn(&format!("{} was made to return an error due to isolation", op_name)); + Ok(()) + } + RejectOpWith::Warning => { + this.emit_diagnostic(NonHaltingDiagnostic::RejectedIsolatedOp(op_name.to_string())); + Ok(()) + } + RejectOpWith::NoWarning => Ok(()), // no warning + } + } + + /// Helper function used inside the shims of foreign functions to assert that the target OS + /// is `target_os`. It panics showing a message with the `name` of the foreign function + /// if this is not the case. + fn assert_target_os(&self, target_os: &str, name: &str) { + assert_eq!( + self.eval_context_ref().tcx.sess.target.os, + target_os, + "`{}` is only available on the `{}` target OS", + name, + target_os, + ) + } + + /// Helper function used inside the shims of foreign functions to assert that the target OS + /// is part of the UNIX family. It panics showing a message with the `name` of the foreign function + /// if this is not the case. + fn assert_target_os_is_unix(&self, name: &str) { + assert!( + target_os_is_unix(self.eval_context_ref().tcx.sess.target.os.as_ref()), + "`{}` is only available for supported UNIX family targets", + name, + ); + } + + /// Get last error variable as a place, lazily allocating thread-local storage for it if + /// necessary. + fn last_error_place(&mut self) -> InterpResult<'tcx, MPlaceTy<'tcx, Provenance>> { + let this = self.eval_context_mut(); + if let Some(errno_place) = this.active_thread_ref().last_error { + Ok(errno_place) + } else { + // Allocate new place, set initial value to 0. + let errno_layout = this.machine.layouts.u32; + let errno_place = this.allocate(errno_layout, MiriMemoryKind::Machine.into())?; + this.write_scalar(Scalar::from_u32(0), &errno_place.into())?; + this.active_thread_mut().last_error = Some(errno_place); + Ok(errno_place) + } + } + + /// Sets the last error variable. + fn set_last_error(&mut self, scalar: Scalar) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + let errno_place = this.last_error_place()?; + this.write_scalar(scalar, &errno_place.into()) + } + + /// Gets the last error variable. + fn get_last_error(&mut self) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + let errno_place = this.last_error_place()?; + this.read_scalar(&errno_place.into()) + } + + /// This function tries to produce the most similar OS error from the `std::io::ErrorKind` + /// as a platform-specific errnum. + fn io_error_to_errnum( + &self, + err_kind: std::io::ErrorKind, + ) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_ref(); + let target = &this.tcx.sess.target; + if target.families.iter().any(|f| f == "unix") { + for &(name, kind) in UNIX_IO_ERROR_TABLE { + if err_kind == kind { + return this.eval_libc(name); + } + } + 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::*; + 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 + ), + }, + ) + } else { + throw_unsup_format!( + "converting io::Error into errnum is unsupported for OS {}", + target.os + ) + } + } + + /// The inverse of `io_error_to_errnum`. + #[allow(clippy::needless_return)] + fn try_errnum_to_io_error( + &self, + errnum: Scalar, + ) -> InterpResult<'tcx, Option> { + let this = self.eval_context_ref(); + let target = &this.tcx.sess.target; + if target.families.iter().any(|f| f == "unix") { + let errnum = errnum.to_i32()?; + for &(name, kind) in UNIX_IO_ERROR_TABLE { + if errnum == this.eval_libc_i32(name)? { + 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 { + throw_unsup_format!( + "converting errnum into io::Error is unsupported for OS {}", + target.os + ) + } + } + + /// Sets the last OS error using a `std::io::ErrorKind`. + fn set_last_error_from_io_error(&mut self, err_kind: std::io::ErrorKind) -> InterpResult<'tcx> { + self.set_last_error(self.io_error_to_errnum(err_kind)?) + } + + /// Helper function that consumes an `std::io::Result` and returns an + /// `InterpResult<'tcx,T>::Ok` instead. In case the result is an error, this function returns + /// `Ok(-1)` and sets the last OS error accordingly. + /// + /// This function uses `T: From` instead of `i32` directly because some IO related + /// functions return different integer types (like `read`, that returns an `i64`). + fn try_unwrap_io_result>( + &mut self, + result: std::io::Result, + ) -> InterpResult<'tcx, T> { + match result { + Ok(ok) => Ok(ok), + Err(e) => { + self.eval_context_mut().set_last_error_from_io_error(e.kind())?; + Ok((-1).into()) + } + } + } + + /// Calculates the MPlaceTy given the offset and layout of an access on an operand + fn deref_operand_and_offset( + &self, + op: &OpTy<'tcx, Provenance>, + offset: u64, + layout: TyAndLayout<'tcx>, + ) -> InterpResult<'tcx, MPlaceTy<'tcx, Provenance>> { + let this = self.eval_context_ref(); + let op_place = this.deref_operand(op)?; + let offset = Size::from_bytes(offset); + + // Ensure that the access is within bounds. + assert!(op_place.layout.size >= offset + layout.size); + let value_place = op_place.offset(offset, layout, this)?; + Ok(value_place) + } + + fn read_scalar_at_offset( + &self, + op: &OpTy<'tcx, Provenance>, + offset: u64, + layout: TyAndLayout<'tcx>, + ) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_ref(); + let value_place = this.deref_operand_and_offset(op, offset, layout)?; + this.read_scalar(&value_place.into()) + } + + fn write_immediate_at_offset( + &mut self, + op: &OpTy<'tcx, Provenance>, + offset: u64, + value: &ImmTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, ()> { + let this = self.eval_context_mut(); + let value_place = this.deref_operand_and_offset(op, offset, value.layout)?; + this.write_immediate(**value, &value_place.into()) + } + + fn write_scalar_at_offset( + &mut self, + op: &OpTy<'tcx, Provenance>, + offset: u64, + value: impl Into>, + layout: TyAndLayout<'tcx>, + ) -> InterpResult<'tcx, ()> { + self.write_immediate_at_offset(op, offset, &ImmTy::from_scalar(value.into(), layout)) + } + + /// Parse a `timespec` struct and return it as a `std::time::Duration`. It returns `None` + /// if the value in the `timespec` struct is invalid. Some libc functions will return + /// `EINVAL` in this case. + fn read_timespec( + &mut self, + tp: &MPlaceTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, Option> { + let this = self.eval_context_mut(); + let seconds_place = this.mplace_field(tp, 0)?; + let seconds_scalar = this.read_scalar(&seconds_place.into())?; + let seconds = seconds_scalar.to_machine_isize(this)?; + let nanoseconds_place = this.mplace_field(tp, 1)?; + let nanoseconds_scalar = this.read_scalar(&nanoseconds_place.into())?; + let nanoseconds = nanoseconds_scalar.to_machine_isize(this)?; + + Ok(try { + // tv_sec must be non-negative. + let seconds: u64 = seconds.try_into().ok()?; + // tv_nsec must be non-negative. + let nanoseconds: u32 = nanoseconds.try_into().ok()?; + if nanoseconds >= 1_000_000_000 { + // tv_nsec must not be greater than 999,999,999. + None? + } + Duration::new(seconds, nanoseconds) + }) + } + + fn read_c_str<'a>(&'a self, ptr: Pointer>) -> InterpResult<'tcx, &'a [u8]> + where + 'tcx: 'a, + 'mir: 'a, + { + let this = self.eval_context_ref(); + let size1 = Size::from_bytes(1); + + // Step 1: determine the length. + let mut len = Size::ZERO; + loop { + // FIXME: We are re-getting the allocation each time around the loop. + // Would be nice if we could somehow "extend" an existing AllocRange. + let alloc = this.get_ptr_alloc(ptr.offset(len, this)?, size1, Align::ONE)?.unwrap(); // not a ZST, so we will get a result + let byte = alloc.read_integer(alloc_range(Size::ZERO, size1))?.to_u8()?; + if byte == 0 { + break; + } else { + len += size1; + } + } + + // Step 2: get the bytes. + this.read_bytes_ptr_strip_provenance(ptr, len) + } + + fn read_wide_str(&self, mut ptr: Pointer>) -> InterpResult<'tcx, Vec> { + let this = self.eval_context_ref(); + let size2 = Size::from_bytes(2); + let align2 = Align::from_bytes(2).unwrap(); + + let mut wchars = Vec::new(); + loop { + // FIXME: We are re-getting the allocation each time around the loop. + // Would be nice if we could somehow "extend" an existing AllocRange. + let alloc = this.get_ptr_alloc(ptr, size2, align2)?.unwrap(); // not a ZST, so we will get a result + let wchar = alloc.read_integer(alloc_range(Size::ZERO, size2))?.to_u16()?; + if wchar == 0 { + break; + } else { + wchars.push(wchar); + ptr = ptr.offset(size2, this)?; + } + } + + Ok(wchars) + } + + /// Check that the ABI is what we expect. + fn check_abi<'a>(&self, abi: Abi, exp_abi: Abi) -> InterpResult<'a, ()> { + if self.eval_context_ref().machine.enforce_abi && abi != exp_abi { + throw_ub_format!( + "calling a function with ABI {} using caller ABI {}", + exp_abi.name(), + abi.name() + ) + } + Ok(()) + } + + fn frame_in_std(&self) -> bool { + let this = self.eval_context_ref(); + let Some(start_fn) = this.tcx.lang_items().start_fn() else { + // no_std situations + return false; + }; + let frame = this.frame(); + // Make an attempt to get at the instance of the function this is inlined from. + let instance: Option<_> = try { + let scope = frame.current_source_info()?.scope; + let inlined_parent = frame.body.source_scopes[scope].inlined_parent_scope?; + let source = &frame.body.source_scopes[inlined_parent]; + source.inlined.expect("inlined_parent_scope points to scope without inline info").0 + }; + // Fall back to the instance of the function itself. + let instance = instance.unwrap_or(frame.instance); + // Now check if this is in the same crate as start_fn. + // As a special exception we also allow unit tests from + // to call these + // shims. + let frame_crate = this.tcx.def_path(instance.def_id()).krate; + frame_crate == this.tcx.def_path(start_fn).krate + || this.tcx.crate_name(frame_crate).as_str() == "std_miri_test" + } + + /// Handler that should be called when unsupported functionality is encountered. + /// This function will either panic within the context of the emulated application + /// or return an error in the Miri process context + /// + /// Return value of `Ok(bool)` indicates whether execution should continue. + fn handle_unsupported>(&mut self, error_msg: S) -> InterpResult<'tcx, ()> { + let this = self.eval_context_mut(); + if this.machine.panic_on_unsupported { + // message is slightly different here to make automated analysis easier + let error_msg = format!("unsupported Miri functionality: {}", error_msg.as_ref()); + this.start_panic(error_msg.as_ref(), StackPopUnwind::Skip)?; + Ok(()) + } else { + throw_unsup_format!("{}", error_msg.as_ref()); + } + } + + fn check_abi_and_shim_symbol_clash( + &mut self, + abi: Abi, + exp_abi: Abi, + link_name: Symbol, + ) -> InterpResult<'tcx, ()> { + self.check_abi(abi, exp_abi)?; + if let Some((body, _)) = self.eval_context_mut().lookup_exported_symbol(link_name)? { + throw_machine_stop!(TerminationInfo::SymbolShimClashing { + link_name, + span: body.span.data(), + }) + } + Ok(()) + } + + fn check_shim<'a, const N: usize>( + &mut self, + abi: Abi, + exp_abi: Abi, + link_name: Symbol, + args: &'a [OpTy<'tcx, Provenance>], + ) -> InterpResult<'tcx, &'a [OpTy<'tcx, Provenance>; N]> + where + &'a [OpTy<'tcx, Provenance>; N]: TryFrom<&'a [OpTy<'tcx, Provenance>]>, + { + self.check_abi_and_shim_symbol_clash(abi, exp_abi, link_name)?; + check_arg_count(args) + } + + /// Mark a machine allocation that was just created as immutable. + fn mark_immutable(&mut self, mplace: &MemPlace) { + let this = self.eval_context_mut(); + // This got just allocated, so there definitely is a pointer here. + let provenance = mplace.ptr.into_pointer_or_addr().unwrap().provenance; + this.alloc_mark_immutable(provenance.get_alloc_id().unwrap()).unwrap(); + } + + fn item_link_name(&self, def_id: DefId) -> Symbol { + let tcx = self.eval_context_ref().tcx; + match tcx.get_attrs(def_id, sym::link_name).filter_map(|a| a.value_str()).next() { + Some(name) => name, + None => tcx.item_name(def_id), + } + } +} + +impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> { + pub fn current_span(&self) -> CurrentSpan<'_, 'mir, 'tcx> { + CurrentSpan { current_frame_idx: None, machine: self } + } +} + +/// A `CurrentSpan` should be created infrequently (ideally once) per interpreter step. It does +/// nothing on creation, but when `CurrentSpan::get` is called, searches the current stack for the +/// topmost frame which corresponds to a local crate, and returns the current span in that frame. +/// The result of that search is cached so that later calls are approximately free. +#[derive(Clone)] +pub struct CurrentSpan<'a, 'mir, 'tcx> { + current_frame_idx: Option, + machine: &'a MiriMachine<'mir, 'tcx>, +} + +impl<'a, 'mir: 'a, 'tcx: 'a + 'mir> CurrentSpan<'a, 'mir, 'tcx> { + pub fn machine(&self) -> &'a MiriMachine<'mir, 'tcx> { + self.machine + } + + /// Get the current span, skipping non-local frames. + /// This function is backed by a cache, and can be assumed to be very fast. + pub fn get(&mut self) -> Span { + let idx = self.current_frame_idx(); + Self::frame_span(self.machine, idx) + } + + /// Similar to `CurrentSpan::get`, but retrieves the parent frame of the first non-local frame. + /// This is useful when we are processing something which occurs on function-entry and we want + /// to point at the call to the function, not the function definition generally. + pub fn get_parent(&mut self) -> Span { + let idx = self.current_frame_idx(); + Self::frame_span(self.machine, idx.wrapping_sub(1)) + } + + fn frame_span(machine: &MiriMachine<'_, '_>, idx: usize) -> Span { + machine + .threads + .active_thread_stack() + .get(idx) + .map(Frame::current_span) + .unwrap_or(rustc_span::DUMMY_SP) + } + + fn current_frame_idx(&mut self) -> usize { + *self + .current_frame_idx + .get_or_insert_with(|| Self::compute_current_frame_index(self.machine)) + } + + // Find the position of the inner-most frame which is part of the crate being + // compiled/executed, part of the Cargo workspace, and is also not #[track_caller]. + #[inline(never)] + fn compute_current_frame_index(machine: &MiriMachine<'_, '_>) -> usize { + machine + .threads + .active_thread_stack() + .iter() + .enumerate() + .rev() + .find_map(|(idx, frame)| { + let def_id = frame.instance.def_id(); + if (def_id.is_local() || machine.local_crates.contains(&def_id.krate)) + && !frame.instance.def.requires_caller_location(machine.tcx) + { + Some(idx) + } else { + None + } + }) + .unwrap_or(0) + } +} + +/// Check that the number of args is what we expect. +pub fn check_arg_count<'a, 'tcx, const N: usize>( + args: &'a [OpTy<'tcx, Provenance>], +) -> InterpResult<'tcx, &'a [OpTy<'tcx, Provenance>; N]> +where + &'a [OpTy<'tcx, Provenance>; N]: TryFrom<&'a [OpTy<'tcx, Provenance>]>, +{ + if let Ok(ops) = args.try_into() { + return Ok(ops); + } + throw_ub_format!("incorrect number of arguments: got {}, expected {}", args.len(), N) +} + +pub fn isolation_abort_error<'tcx>(name: &str) -> InterpResult<'tcx> { + throw_machine_stop!(TerminationInfo::UnsupportedInIsolation(format!( + "{} not available when isolation is enabled", + name, + ))) +} + +/// Retrieve the list of local crates that should have been passed by cargo-miri in +/// MIRI_LOCAL_CRATES and turn them into `CrateNum`s. +pub fn get_local_crates(tcx: TyCtxt<'_>) -> Vec { + // Convert the local crate names from the passed-in config into CrateNums so that they can + // be looked up quickly during execution + let local_crate_names = std::env::var("MIRI_LOCAL_CRATES") + .map(|crates| crates.split(',').map(|krate| krate.to_string()).collect::>()) + .unwrap_or_default(); + let mut local_crates = Vec::new(); + for &crate_num in tcx.crates(()) { + let name = tcx.crate_name(crate_num); + let name = name.as_str(); + if local_crate_names.iter().any(|local_name| local_name == name) { + local_crates.push(crate_num); + } + } + local_crates +} + +/// Helper function used inside the shims of foreign functions to check that +/// `target_os` is a supported UNIX OS. +pub fn target_os_is_unix(target_os: &str) -> bool { + matches!(target_os, "linux" | "macos" | "freebsd" | "android") +} diff --git a/src/tools/miri/src/helpers/convert.rs b/src/tools/miri/src/helpers/convert.rs new file mode 100644 index 0000000000000..4506fe47495d0 --- /dev/null +++ b/src/tools/miri/src/helpers/convert.rs @@ -0,0 +1,49 @@ +use implementations::NarrowerThan; + +/// Replacement for `as` casts going from wide integer to narrower integer. +/// +/// # Example +/// +/// ```ignore +/// let x = 99_u64; +/// let lo = x.truncate::(); +/// // lo is of type u16, equivalent to `x as u16`. +/// ``` +pub(crate) trait Truncate: Sized { + fn truncate(self) -> To + where + To: NarrowerThan, + { + NarrowerThan::truncate_from(self) + } +} + +impl Truncate for u16 {} +impl Truncate for u32 {} +impl Truncate for u64 {} +impl Truncate for u128 {} + +mod implementations { + pub(crate) trait NarrowerThan { + fn truncate_from(wide: T) -> Self; + } + + macro_rules! impl_narrower_than { + ($(NarrowerThan<{$($ty:ty),*}> for $self:ty)*) => { + $($( + impl NarrowerThan<$ty> for $self { + fn truncate_from(wide: $ty) -> Self { + wide as Self + } + } + )*)* + }; + } + + impl_narrower_than! { + NarrowerThan<{u128, u64, u32, u16}> for u8 + NarrowerThan<{u128, u64, u32}> for u16 + NarrowerThan<{u128, u64}> for u32 + NarrowerThan<{u128}> for u64 + } +} diff --git a/src/tools/miri/src/intptrcast.rs b/src/tools/miri/src/intptrcast.rs new file mode 100644 index 0000000000000..b9e5def8fa7cb --- /dev/null +++ b/src/tools/miri/src/intptrcast.rs @@ -0,0 +1,260 @@ +use std::cell::RefCell; +use std::cmp::max; +use std::collections::hash_map::Entry; + +use log::trace; +use rand::Rng; + +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_span::Span; +use rustc_target::abi::{HasDataLayout, Size}; + +use crate::*; + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum ProvenanceMode { + /// We support `expose_addr`/`from_exposed_addr` via "wildcard" provenance. + /// However, we want on `from_exposed_addr` to alert the user of the precision loss. + Default, + /// Like `Default`, but without the warning. + Permissive, + /// We error on `from_exposed_addr`, ensuring no precision loss. + Strict, +} + +pub type GlobalState = RefCell; + +#[derive(Clone, Debug)] +pub struct GlobalStateInner { + /// This is used as a map between the address of each allocation and its `AllocId`. + /// It is always sorted + int_to_ptr_map: Vec<(u64, AllocId)>, + /// The base address for each allocation. We cannot put that into + /// `AllocExtra` because function pointers also have a base address, and + /// they do not have an `AllocExtra`. + /// This is the inverse of `int_to_ptr_map`. + base_addr: FxHashMap, + /// Whether an allocation has been exposed or not. This cannot be put + /// into `AllocExtra` for the same reason as `base_addr`. + exposed: FxHashSet, + /// This is used as a memory address when a new pointer is casted to an integer. It + /// is always larger than any address that was previously made part of a block. + next_base_addr: u64, + /// The provenance to use for int2ptr casts + provenance_mode: ProvenanceMode, +} + +impl GlobalStateInner { + pub fn new(config: &MiriConfig) -> Self { + GlobalStateInner { + int_to_ptr_map: Vec::default(), + base_addr: FxHashMap::default(), + exposed: FxHashSet::default(), + next_base_addr: STACK_ADDR, + provenance_mode: config.provenance_mode, + } + } +} + +impl<'mir, 'tcx> GlobalStateInner { + // Returns the exposed `AllocId` that corresponds to the specified addr, + // or `None` if the addr is out of bounds + fn alloc_id_from_addr(ecx: &MiriInterpCx<'mir, 'tcx>, addr: u64) -> Option { + let global_state = ecx.machine.intptrcast.borrow(); + assert!(global_state.provenance_mode != ProvenanceMode::Strict); + + let pos = global_state.int_to_ptr_map.binary_search_by_key(&addr, |(addr, _)| *addr); + + // Determine the in-bounds provenance for this pointer. + // (This is only called on an actual access, so in-bounds is the only possible kind of provenance.) + let alloc_id = match pos { + Ok(pos) => Some(global_state.int_to_ptr_map[pos].1), + Err(0) => None, + Err(pos) => { + // This is the largest of the adresses smaller than `int`, + // i.e. the greatest lower bound (glb) + let (glb, alloc_id) = global_state.int_to_ptr_map[pos - 1]; + // This never overflows because `addr >= glb` + let offset = addr - glb; + // If the offset exceeds the size of the allocation, don't use this `alloc_id`. + let size = ecx.get_alloc_info(alloc_id).0; + if offset <= size.bytes() { Some(alloc_id) } else { None } + } + }?; + + // We only use this provenance if it has been exposed, *and* is still live. + if global_state.exposed.contains(&alloc_id) { + let (_size, _align, kind) = ecx.get_alloc_info(alloc_id); + match kind { + AllocKind::LiveData | AllocKind::Function | AllocKind::VTable => { + return Some(alloc_id); + } + AllocKind::Dead => {} + } + } + + None + } + + pub fn expose_ptr( + ecx: &mut MiriInterpCx<'mir, 'tcx>, + alloc_id: AllocId, + sb: SbTag, + ) -> InterpResult<'tcx> { + let global_state = ecx.machine.intptrcast.get_mut(); + // In strict mode, we don't need this, so we can save some cycles by not tracking it. + if global_state.provenance_mode != ProvenanceMode::Strict { + trace!("Exposing allocation id {alloc_id:?}"); + global_state.exposed.insert(alloc_id); + if ecx.machine.stacked_borrows.is_some() { + ecx.expose_tag(alloc_id, sb)?; + } + } + Ok(()) + } + + pub fn ptr_from_addr_transmute( + _ecx: &MiriInterpCx<'mir, 'tcx>, + addr: u64, + ) -> Pointer> { + trace!("Transmuting {:#x} to a pointer", addr); + + // We consider transmuted pointers to be "invalid" (`None` provenance). + Pointer::new(None, Size::from_bytes(addr)) + } + + pub fn ptr_from_addr_cast( + ecx: &MiriInterpCx<'mir, 'tcx>, + addr: u64, + ) -> InterpResult<'tcx, Pointer>> { + trace!("Casting {:#x} to a pointer", addr); + + let global_state = ecx.machine.intptrcast.borrow(); + + match global_state.provenance_mode { + ProvenanceMode::Default => { + // The first time this happens at a particular location, print a warning. + thread_local! { + // `Span` is non-`Send`, so we use a thread-local instead. + static PAST_WARNINGS: RefCell> = RefCell::default(); + } + PAST_WARNINGS.with_borrow_mut(|past_warnings| { + let first = past_warnings.is_empty(); + if past_warnings.insert(ecx.cur_span()) { + // Newly inserted, so first time we see this span. + ecx.emit_diagnostic(NonHaltingDiagnostic::Int2Ptr { details: first }); + } + }); + } + ProvenanceMode::Strict => { + throw_machine_stop!(TerminationInfo::Int2PtrWithStrictProvenance); + } + ProvenanceMode::Permissive => {} + } + + // This is how wildcard pointers are born. + Ok(Pointer::new(Some(Provenance::Wildcard), Size::from_bytes(addr))) + } + + fn alloc_base_addr(ecx: &MiriInterpCx<'mir, 'tcx>, alloc_id: AllocId) -> u64 { + let mut global_state = ecx.machine.intptrcast.borrow_mut(); + let global_state = &mut *global_state; + + match global_state.base_addr.entry(alloc_id) { + Entry::Occupied(entry) => *entry.get(), + Entry::Vacant(entry) => { + // There is nothing wrong with a raw pointer being cast to an integer only after + // it became dangling. Hence we allow dead allocations. + let (size, align, _kind) = ecx.get_alloc_info(alloc_id); + + // This allocation does not have a base address yet, pick one. + // Leave some space to the previous allocation, to give it some chance to be less aligned. + let slack = { + let mut rng = ecx.machine.rng.borrow_mut(); + // This means that `(global_state.next_base_addr + slack) % 16` is uniformly distributed. + rng.gen_range(0..16) + }; + // From next_base_addr + slack, round up to adjust for alignment. + let base_addr = global_state.next_base_addr.checked_add(slack).unwrap(); + let base_addr = Self::align_addr(base_addr, align.bytes()); + entry.insert(base_addr); + trace!( + "Assigning base address {:#x} to allocation {:?} (size: {}, align: {}, slack: {})", + base_addr, + alloc_id, + size.bytes(), + align.bytes(), + slack, + ); + + // Remember next base address. If this allocation is zero-sized, leave a gap + // of at least 1 to avoid two allocations having the same base address. + // (The logic in `alloc_id_from_addr` assumes unique addresses, and different + // function/vtable pointers need to be distinguishable!) + global_state.next_base_addr = base_addr.checked_add(max(size.bytes(), 1)).unwrap(); + // Given that `next_base_addr` increases in each allocation, pushing the + // corresponding tuple keeps `int_to_ptr_map` sorted + global_state.int_to_ptr_map.push((base_addr, alloc_id)); + + base_addr + } + } + } + + /// Convert a relative (tcx) pointer to an absolute address. + pub fn rel_ptr_to_addr(ecx: &MiriInterpCx<'mir, 'tcx>, ptr: Pointer) -> u64 { + let (alloc_id, offset) = ptr.into_parts(); // offset is relative (AllocId provenance) + let base_addr = GlobalStateInner::alloc_base_addr(ecx, alloc_id); + + // Add offset with the right kind of pointer-overflowing arithmetic. + let dl = ecx.data_layout(); + dl.overflowing_offset(base_addr, offset.bytes()).0 + } + + /// When a pointer is used for a memory access, this computes where in which allocation the + /// access is going. + pub fn abs_ptr_to_rel( + ecx: &MiriInterpCx<'mir, 'tcx>, + ptr: Pointer, + ) -> Option<(AllocId, Size)> { + let (tag, addr) = ptr.into_parts(); // addr is absolute (Tag provenance) + + let alloc_id = if let Provenance::Concrete { alloc_id, .. } = tag { + alloc_id + } else { + // A wildcard pointer. + GlobalStateInner::alloc_id_from_addr(ecx, addr.bytes())? + }; + + let base_addr = GlobalStateInner::alloc_base_addr(ecx, alloc_id); + + // Wrapping "addr - base_addr" + let dl = ecx.data_layout(); + #[allow(clippy::cast_possible_wrap)] // we want to wrap here + let neg_base_addr = (base_addr as i64).wrapping_neg(); + Some(( + alloc_id, + Size::from_bytes(dl.overflowing_signed_offset(addr.bytes(), neg_base_addr).0), + )) + } + + /// Shifts `addr` to make it aligned with `align` by rounding `addr` to the smallest multiple + /// of `align` that is larger or equal to `addr` + fn align_addr(addr: u64, align: u64) -> u64 { + match addr % align { + 0 => addr, + rem => addr.checked_add(align).unwrap() - rem, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_align_addr() { + assert_eq!(GlobalStateInner::align_addr(37, 4), 40); + assert_eq!(GlobalStateInner::align_addr(44, 4), 44); + } +} diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs new file mode 100644 index 0000000000000..6006d6c89dbca --- /dev/null +++ b/src/tools/miri/src/lib.rs @@ -0,0 +1,126 @@ +#![feature(rustc_private)] +#![feature(map_first_last)] +#![feature(map_try_insert)] +#![feature(never_type)] +#![feature(try_blocks)] +#![feature(io_error_more)] +#![feature(int_log)] +#![feature(variant_count)] +#![feature(yeet_expr)] +#![feature(is_some_with)] +#![feature(nonzero_ops)] +#![feature(local_key_cell_methods)] +// Configure clippy and other lints +#![allow( + clippy::collapsible_else_if, + clippy::collapsible_if, + clippy::comparison_chain, + clippy::enum_variant_names, + clippy::field_reassign_with_default, + clippy::manual_map, + clippy::new_without_default, + clippy::single_match, + clippy::useless_format, + clippy::derive_partial_eq_without_eq, + clippy::derive_hash_xor_eq, + clippy::too_many_arguments, + clippy::type_complexity, + clippy::single_element_loop, + clippy::needless_return, + // We are not implementing queries here so it's fine + rustc::potential_query_instability +)] +#![warn( + rust_2018_idioms, + clippy::cast_possible_wrap, // unsigned -> signed + clippy::cast_sign_loss, // signed -> unsigned + clippy::cast_lossless, + clippy::cast_possible_truncation, +)] + +extern crate rustc_apfloat; +extern crate rustc_ast; +#[macro_use] +extern crate rustc_middle; +extern crate rustc_const_eval; +extern crate rustc_data_structures; +extern crate rustc_hir; +extern crate rustc_index; +extern crate rustc_session; +extern crate rustc_span; +extern crate rustc_target; + +mod clock; +mod concurrency; +mod diagnostics; +mod eval; +mod helpers; +mod intptrcast; +mod machine; +mod mono_hash_map; +mod operator; +mod range_map; +mod shims; +mod stacked_borrows; +mod tag_gc; + +// Establish a "crate-wide prelude": we often import `crate::*`. + +// Make all those symbols available in the same place as our own. +pub use rustc_const_eval::interpret::*; +// Resolve ambiguity. +pub use rustc_const_eval::interpret::{self, AllocMap, PlaceTy, Provenance as _}; + +pub use crate::shims::dlsym::{Dlsym, EvalContextExt as _}; +pub use crate::shims::env::{EnvVars, EvalContextExt as _}; +pub use crate::shims::foreign_items::EvalContextExt as _; +pub use crate::shims::intrinsics::EvalContextExt as _; +pub use crate::shims::os_str::EvalContextExt as _; +pub use crate::shims::panic::{CatchUnwindData, EvalContextExt as _}; +pub use crate::shims::time::EvalContextExt as _; +pub use crate::shims::tls::{EvalContextExt as _, TlsData}; +pub use crate::shims::EvalContextExt as _; + +pub use crate::clock::{Clock, Instant}; +pub use crate::concurrency::{ + data_race::{ + AtomicFenceOrd, AtomicReadOrd, AtomicRwOrd, AtomicWriteOrd, + EvalContextExt as DataRaceEvalContextExt, + }, + sync::{CondvarId, EvalContextExt as SyncEvalContextExt, MutexId, RwLockId}, + thread::{ + EvalContextExt as ThreadsEvalContextExt, SchedulingAction, ThreadId, ThreadManager, + ThreadState, Time, + }, +}; +pub use crate::diagnostics::{ + report_error, EvalContextExt as DiagnosticsEvalContextExt, NonHaltingDiagnostic, + TerminationInfo, +}; +pub use crate::eval::{ + create_ecx, eval_entry, AlignmentCheck, BacktraceStyle, IsolatedOp, MiriConfig, RejectOpWith, +}; +pub use crate::helpers::{CurrentSpan, EvalContextExt as HelpersEvalContextExt}; +pub use crate::intptrcast::ProvenanceMode; +pub use crate::machine::{ + AllocExtra, FrameData, MiriInterpCx, MiriInterpCxExt, MiriMachine, MiriMemoryKind, Provenance, + ProvenanceExtra, NUM_CPUS, PAGE_SIZE, STACK_ADDR, STACK_SIZE, +}; +pub use crate::mono_hash_map::MonoHashMap; +pub use crate::operator::EvalContextExt as OperatorEvalContextExt; +pub use crate::range_map::RangeMap; +pub use crate::stacked_borrows::{ + CallId, EvalContextExt as StackedBorEvalContextExt, Item, Permission, SbTag, Stack, Stacks, +}; +pub use crate::tag_gc::EvalContextExt as _; + +/// Insert rustc arguments at the beginning of the argument list that Miri wants to be +/// set per default, for maximal validation power. +pub const MIRI_DEFAULT_ARGS: &[&str] = &[ + "-Zalways-encode-mir", + "-Zmir-emit-retag", + "-Zmir-opt-level=0", + "--cfg=miri", + "-Cdebug-assertions=on", + "-Zextra-const-ub-checks", +]; diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs new file mode 100644 index 0000000000000..dcfb998c5645d --- /dev/null +++ b/src/tools/miri/src/machine.rs @@ -0,0 +1,1077 @@ +//! Global machine state as well as implementation of the interpreter engine +//! `Machine` trait. + +use std::borrow::Cow; +use std::cell::RefCell; +use std::fmt; + +use rand::rngs::StdRng; +use rand::SeedableRng; + +use rustc_ast::ast::Mutability; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +#[allow(unused)] +use rustc_data_structures::static_assert_size; +use rustc_middle::{ + mir, + ty::{ + self, + layout::{LayoutCx, LayoutError, LayoutOf, TyAndLayout}, + Instance, Ty, TyCtxt, TypeAndMut, + }, +}; +use rustc_span::def_id::{CrateNum, DefId}; +use rustc_span::Symbol; +use rustc_target::abi::Size; +use rustc_target::spec::abi::Abi; + +use crate::{ + concurrency::{data_race, weak_memory}, + shims::unix::FileHandler, + *, +}; + +// Some global facts about the emulated machine. +pub const PAGE_SIZE: u64 = 4 * 1024; // FIXME: adjust to target architecture +pub const STACK_ADDR: u64 = 32 * PAGE_SIZE; // not really about the "stack", but where we start assigning integer addresses to allocations +pub const STACK_SIZE: u64 = 16 * PAGE_SIZE; // whatever +pub const NUM_CPUS: u64 = 1; + +/// Extra data stored with each stack frame +pub struct FrameData<'tcx> { + /// Extra data for Stacked Borrows. + pub stacked_borrows: Option, + + /// If this is Some(), then this is a special "catch unwind" frame (the frame of `try_fn` + /// called by `try`). When this frame is popped during unwinding a panic, + /// we stop unwinding, use the `CatchUnwindData` to handle catching. + pub catch_unwind: Option>, + + /// If `measureme` profiling is enabled, holds timing information + /// for the start of this frame. When we finish executing this frame, + /// we use this to register a completed event with `measureme`. + pub timing: Option, +} + +impl<'tcx> std::fmt::Debug for FrameData<'tcx> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // Omitting `timing`, it does not support `Debug`. + let FrameData { stacked_borrows, catch_unwind, timing: _ } = self; + f.debug_struct("FrameData") + .field("stacked_borrows", stacked_borrows) + .field("catch_unwind", catch_unwind) + .finish() + } +} + +/// Extra memory kinds +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum MiriMemoryKind { + /// `__rust_alloc` memory. + Rust, + /// `malloc` memory. + C, + /// Windows `HeapAlloc` memory. + WinHeap, + /// Memory for args, errno, and other parts of the machine-managed environment. + /// This memory may leak. + Machine, + /// Memory allocated by the runtime (e.g. env vars). Separate from `Machine` + /// because we clean it up and leak-check it. + Runtime, + /// Globals copied from `tcx`. + /// This memory may leak. + Global, + /// Memory for extern statics. + /// This memory may leak. + ExternStatic, + /// Memory for thread-local statics. + /// This memory may leak. + Tls, +} + +impl From for MemoryKind { + #[inline(always)] + fn from(kind: MiriMemoryKind) -> MemoryKind { + MemoryKind::Machine(kind) + } +} + +impl MayLeak for MiriMemoryKind { + #[inline(always)] + fn may_leak(self) -> bool { + use self::MiriMemoryKind::*; + match self { + Rust | C | WinHeap | Runtime => false, + Machine | Global | ExternStatic | Tls => true, + } + } +} + +impl fmt::Display for MiriMemoryKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use self::MiriMemoryKind::*; + match self { + Rust => write!(f, "Rust heap"), + C => write!(f, "C heap"), + WinHeap => write!(f, "Windows heap"), + Machine => write!(f, "machine-managed memory"), + Runtime => write!(f, "language runtime memory"), + Global => write!(f, "global (static or const)"), + ExternStatic => write!(f, "extern static"), + Tls => write!(f, "thread-local static"), + } + } +} + +/// Pointer provenance. +#[derive(Debug, Clone, Copy)] +pub enum Provenance { + Concrete { + alloc_id: AllocId, + /// Stacked Borrows tag. + sb: SbTag, + }, + Wildcard, +} + +// This needs to be `Eq`+`Hash` because the `Machine` trait needs that because validity checking +// *might* be recursive and then it has to track which places have already been visited. +// However, comparing provenance is meaningless, since `Wildcard` might be any provenance -- and of +// course we don't actually do recursive checking. +// We could change `RefTracking` to strip provenance for its `seen` set but that type is generic so that is quite annoying. +// Instead owe add the required instances but make them panic. +impl PartialEq for Provenance { + fn eq(&self, _other: &Self) -> bool { + panic!("Provenance must not be compared") + } +} +impl Eq for Provenance {} +impl std::hash::Hash for Provenance { + fn hash(&self, _state: &mut H) { + panic!("Provenance must not be hashed") + } +} + +/// The "extra" information a pointer has over a regular AllocId. +#[derive(Copy, Clone, PartialEq)] +pub enum ProvenanceExtra { + Concrete(SbTag), + Wildcard, +} + +#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))] +static_assert_size!(Pointer, 24); +// FIXME: this would with in 24bytes but layout optimizations are not smart enough +// #[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))] +//static_assert_size!(Pointer>, 24); +#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))] +static_assert_size!(Scalar, 32); + +impl interpret::Provenance for Provenance { + /// We use absolute addresses in the `offset` of a `Pointer`. + const OFFSET_IS_ADDR: bool = true; + + /// We cannot err on partial overwrites, it happens too often in practice (due to unions). + const ERR_ON_PARTIAL_PTR_OVERWRITE: bool = false; + + fn fmt(ptr: &Pointer, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let (prov, addr) = ptr.into_parts(); // address is absolute + write!(f, "{:#x}", addr.bytes())?; + + match prov { + Provenance::Concrete { alloc_id, sb } => { + // Forward `alternate` flag to `alloc_id` printing. + if f.alternate() { + write!(f, "[{:#?}]", alloc_id)?; + } else { + write!(f, "[{:?}]", alloc_id)?; + } + // Print Stacked Borrows tag. + write!(f, "{:?}", sb)?; + } + Provenance::Wildcard => { + write!(f, "[wildcard]")?; + } + } + + Ok(()) + } + + fn get_alloc_id(self) -> Option { + match self { + Provenance::Concrete { alloc_id, .. } => Some(alloc_id), + Provenance::Wildcard => None, + } + } + + fn join(left: Option, right: Option) -> Option { + match (left, right) { + // If both are the *same* concrete tag, that is the result. + ( + Some(Provenance::Concrete { alloc_id: left_alloc, sb: left_sb }), + Some(Provenance::Concrete { alloc_id: right_alloc, sb: right_sb }), + ) if left_alloc == right_alloc && left_sb == right_sb => left, + // If one side is a wildcard, the best possible outcome is that it is equal to the other + // one, and we use that. + (Some(Provenance::Wildcard), o) | (o, Some(Provenance::Wildcard)) => o, + // Otherwise, fall back to `None`. + _ => None, + } + } +} + +impl fmt::Debug for ProvenanceExtra { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ProvenanceExtra::Concrete(pid) => write!(f, "{pid:?}"), + ProvenanceExtra::Wildcard => write!(f, ""), + } + } +} + +impl ProvenanceExtra { + pub fn and_then(self, f: impl FnOnce(SbTag) -> Option) -> Option { + match self { + ProvenanceExtra::Concrete(pid) => f(pid), + ProvenanceExtra::Wildcard => None, + } + } +} + +/// Extra per-allocation data +#[derive(Debug, Clone)] +pub struct AllocExtra { + /// Stacked Borrows state is only added if it is enabled. + pub stacked_borrows: Option, + /// Data race detection via the use of a vector-clock, + /// this is only added if it is enabled. + pub data_race: Option, + /// Weak memory emulation via the use of store buffers, + /// this is only added if it is enabled. + pub weak_memory: Option, +} + +/// Precomputed layouts of primitive types +pub struct PrimitiveLayouts<'tcx> { + pub unit: TyAndLayout<'tcx>, + pub i8: TyAndLayout<'tcx>, + pub i16: TyAndLayout<'tcx>, + pub i32: TyAndLayout<'tcx>, + pub isize: TyAndLayout<'tcx>, + pub u8: TyAndLayout<'tcx>, + pub u16: TyAndLayout<'tcx>, + pub u32: TyAndLayout<'tcx>, + pub usize: TyAndLayout<'tcx>, + pub bool: TyAndLayout<'tcx>, + pub mut_raw_ptr: TyAndLayout<'tcx>, // *mut () + pub const_raw_ptr: TyAndLayout<'tcx>, // *const () +} + +impl<'mir, 'tcx: 'mir> PrimitiveLayouts<'tcx> { + fn new(layout_cx: LayoutCx<'tcx, TyCtxt<'tcx>>) -> Result> { + let tcx = layout_cx.tcx; + let mut_raw_ptr = tcx.mk_ptr(TypeAndMut { ty: tcx.types.unit, mutbl: Mutability::Mut }); + let const_raw_ptr = tcx.mk_ptr(TypeAndMut { ty: tcx.types.unit, mutbl: Mutability::Not }); + Ok(Self { + unit: layout_cx.layout_of(tcx.mk_unit())?, + i8: layout_cx.layout_of(tcx.types.i8)?, + i16: layout_cx.layout_of(tcx.types.i16)?, + i32: layout_cx.layout_of(tcx.types.i32)?, + isize: layout_cx.layout_of(tcx.types.isize)?, + u8: layout_cx.layout_of(tcx.types.u8)?, + u16: layout_cx.layout_of(tcx.types.u16)?, + u32: layout_cx.layout_of(tcx.types.u32)?, + usize: layout_cx.layout_of(tcx.types.usize)?, + bool: layout_cx.layout_of(tcx.types.bool)?, + mut_raw_ptr: layout_cx.layout_of(mut_raw_ptr)?, + const_raw_ptr: layout_cx.layout_of(const_raw_ptr)?, + }) + } +} + +/// The machine itself. +pub struct MiriMachine<'mir, 'tcx> { + // We carry a copy of the global `TyCtxt` for convenience, so methods taking just `&Evaluator` have `tcx` access. + pub tcx: TyCtxt<'tcx>, + + /// Stacked Borrows global data. + pub stacked_borrows: Option, + + /// Data race detector global data. + pub data_race: Option, + + /// Ptr-int-cast module global data. + pub intptrcast: intptrcast::GlobalState, + + /// Environment variables set by `setenv`. + /// Miri does not expose env vars from the host to the emulated program. + pub(crate) env_vars: EnvVars<'tcx>, + + /// Program arguments (`Option` because we can only initialize them after creating the ecx). + /// These are *pointers* to argc/argv because macOS. + /// We also need the full command line as one string because of Windows. + pub(crate) argc: Option>, + pub(crate) argv: Option>, + pub(crate) cmd_line: Option>, + + /// TLS state. + pub(crate) tls: TlsData<'tcx>, + + /// What should Miri do when an op requires communicating with the host, + /// such as accessing host env vars, random number generation, and + /// file system access. + pub(crate) isolated_op: IsolatedOp, + + /// Whether to enforce the validity invariant. + pub(crate) validate: bool, + + /// Whether to enforce [ABI](Abi) of function calls. + pub(crate) enforce_abi: bool, + + /// The table of file descriptors. + pub(crate) file_handler: shims::unix::FileHandler, + /// The table of directory descriptors. + pub(crate) dir_handler: shims::unix::DirHandler, + + /// This machine's monotone clock. + pub(crate) clock: Clock, + + /// The set of threads. + pub(crate) threads: ThreadManager<'mir, 'tcx>, + + /// Precomputed `TyLayout`s for primitive data types that are commonly used inside Miri. + pub(crate) layouts: PrimitiveLayouts<'tcx>, + + /// Allocations that are considered roots of static memory (that may leak). + pub(crate) static_roots: Vec, + + /// The `measureme` profiler used to record timing information about + /// the emulated program. + profiler: Option, + /// Used with `profiler` to cache the `StringId`s for event names + /// uesd with `measureme`. + string_cache: FxHashMap, + + /// Cache of `Instance` exported under the given `Symbol` name. + /// `None` means no `Instance` exported under the given name is found. + pub(crate) exported_symbols_cache: FxHashMap>>, + + /// Whether to raise a panic in the context of the evaluated process when unsupported + /// functionality is encountered. If `false`, an error is propagated in the Miri application context + /// instead (default behavior) + pub(crate) panic_on_unsupported: bool, + + /// Equivalent setting as RUST_BACKTRACE on encountering an error. + pub(crate) backtrace_style: BacktraceStyle, + + /// Crates which are considered local for the purposes of error reporting. + pub(crate) local_crates: Vec, + + /// Mapping extern static names to their base pointer. + extern_statics: FxHashMap>, + + /// The random number generator used for resolving non-determinism. + /// Needs to be queried by ptr_to_int, hence needs interior mutability. + pub(crate) rng: RefCell, + + /// The allocation IDs to report when they are being allocated + /// (helps for debugging memory leaks and use after free bugs). + tracked_alloc_ids: FxHashSet, + + /// Controls whether alignment of memory accesses is being checked. + pub(crate) check_alignment: AlignmentCheck, + + /// Failure rate of compare_exchange_weak, between 0.0 and 1.0 + pub(crate) cmpxchg_weak_failure_rate: f64, + + /// Corresponds to -Zmiri-mute-stdout-stderr and doesn't write the output but acts as if it succeeded. + pub(crate) mute_stdout_stderr: bool, + + /// Whether weak memory emulation is enabled + pub(crate) weak_memory: bool, + + /// The probability of the active thread being preempted at the end of each basic block. + pub(crate) preemption_rate: f64, + + /// If `Some`, we will report the current stack every N basic blocks. + pub(crate) report_progress: Option, + // The total number of blocks that have been executed. + pub(crate) basic_block_count: u64, + + /// Handle of the optional shared object file for external functions. + #[cfg(unix)] + pub external_so_lib: Option<(libloading::Library, std::path::PathBuf)>, + + /// Run a garbage collector for SbTags every N basic blocks. + pub(crate) gc_interval: u32, + /// The number of blocks that passed since the last SbTag GC pass. + pub(crate) since_gc: u32, +} + +impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> { + pub(crate) fn new(config: &MiriConfig, layout_cx: LayoutCx<'tcx, TyCtxt<'tcx>>) -> Self { + let local_crates = helpers::get_local_crates(layout_cx.tcx); + let layouts = + PrimitiveLayouts::new(layout_cx).expect("Couldn't get layouts of primitive types"); + let profiler = config.measureme_out.as_ref().map(|out| { + measureme::Profiler::new(out).expect("Couldn't create `measureme` profiler") + }); + let rng = StdRng::seed_from_u64(config.seed.unwrap_or(0)); + let stacked_borrows = config.stacked_borrows.then(|| { + RefCell::new(stacked_borrows::GlobalStateInner::new( + config.tracked_pointer_tags.clone(), + config.tracked_call_ids.clone(), + config.retag_fields, + )) + }); + let data_race = config.data_race_detector.then(|| data_race::GlobalState::new(config)); + MiriMachine { + tcx: layout_cx.tcx, + stacked_borrows, + data_race, + intptrcast: RefCell::new(intptrcast::GlobalStateInner::new(config)), + // `env_vars` depends on a full interpreter so we cannot properly initialize it yet. + env_vars: EnvVars::default(), + argc: None, + argv: None, + cmd_line: None, + tls: TlsData::default(), + isolated_op: config.isolated_op, + validate: config.validate, + enforce_abi: config.check_abi, + file_handler: FileHandler::new(config.mute_stdout_stderr), + dir_handler: Default::default(), + layouts, + threads: ThreadManager::default(), + static_roots: Vec::new(), + profiler, + string_cache: Default::default(), + exported_symbols_cache: FxHashMap::default(), + panic_on_unsupported: config.panic_on_unsupported, + backtrace_style: config.backtrace_style, + local_crates, + extern_statics: FxHashMap::default(), + rng: RefCell::new(rng), + tracked_alloc_ids: config.tracked_alloc_ids.clone(), + check_alignment: config.check_alignment, + cmpxchg_weak_failure_rate: config.cmpxchg_weak_failure_rate, + mute_stdout_stderr: config.mute_stdout_stderr, + weak_memory: config.weak_memory_emulation, + preemption_rate: config.preemption_rate, + report_progress: config.report_progress, + basic_block_count: 0, + clock: Clock::new(config.isolated_op == IsolatedOp::Allow), + #[cfg(unix)] + external_so_lib: config.external_so_file.as_ref().map(|lib_file_path| { + let target_triple = layout_cx.tcx.sess.opts.target_triple.triple(); + // Check if host target == the session target. + if env!("TARGET") != target_triple { + panic!( + "calling external C functions in linked .so file requires host and target to be the same: host={}, target={}", + env!("TARGET"), + target_triple, + ); + } + // Note: it is the user's responsibility to provide a correct SO file. + // WATCH OUT: If an invalid/incorrect SO file is specified, this can cause + // undefined behaviour in Miri itself! + ( + unsafe { + libloading::Library::new(lib_file_path) + .expect("failed to read specified extern shared object file") + }, + lib_file_path.clone(), + ) + }), + gc_interval: config.gc_interval, + since_gc: 0, + } + } + + pub(crate) fn late_init( + this: &mut MiriInterpCx<'mir, 'tcx>, + config: &MiriConfig, + ) -> InterpResult<'tcx> { + EnvVars::init(this, config)?; + MiriMachine::init_extern_statics(this)?; + ThreadManager::init(this); + Ok(()) + } + + fn add_extern_static( + this: &mut MiriInterpCx<'mir, 'tcx>, + name: &str, + ptr: Pointer>, + ) { + // This got just allocated, so there definitely is a pointer here. + let ptr = ptr.into_pointer_or_addr().unwrap(); + this.machine.extern_statics.try_insert(Symbol::intern(name), ptr).unwrap(); + } + + fn alloc_extern_static( + this: &mut MiriInterpCx<'mir, 'tcx>, + name: &str, + val: ImmTy<'tcx, Provenance>, + ) -> InterpResult<'tcx> { + let place = this.allocate(val.layout, MiriMemoryKind::ExternStatic.into())?; + this.write_immediate(*val, &place.into())?; + Self::add_extern_static(this, name, place.ptr); + Ok(()) + } + + /// Sets up the "extern statics" for this machine. + fn init_extern_statics(this: &mut MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx> { + match this.tcx.sess.target.os.as_ref() { + "linux" => { + // "environ" + Self::add_extern_static( + this, + "environ", + this.machine.env_vars.environ.unwrap().ptr, + ); + // A couple zero-initialized pointer-sized extern statics. + // Most of them are for weak symbols, which we all set to null (indicating that the + // symbol is not supported, and triggering fallback code which ends up calling a + // syscall that we do support). + for name in &["__cxa_thread_atexit_impl", "getrandom", "statx", "__clock_gettime64"] + { + let val = ImmTy::from_int(0, this.machine.layouts.usize); + Self::alloc_extern_static(this, name, val)?; + } + } + "freebsd" => { + // "environ" + Self::add_extern_static( + this, + "environ", + this.machine.env_vars.environ.unwrap().ptr, + ); + } + "android" => { + // "signal" + let layout = this.machine.layouts.const_raw_ptr; + let dlsym = Dlsym::from_str("signal".as_bytes(), &this.tcx.sess.target.os)? + .expect("`signal` must be an actual dlsym on android"); + let ptr = this.create_fn_alloc_ptr(FnVal::Other(dlsym)); + let val = ImmTy::from_scalar(Scalar::from_pointer(ptr, this), layout); + Self::alloc_extern_static(this, "signal", val)?; + // A couple zero-initialized pointer-sized extern statics. + // Most of them are for weak symbols, which we all set to null (indicating that the + // symbol is not supported, and triggering fallback code.) + for name in &["bsd_signal"] { + let val = ImmTy::from_int(0, this.machine.layouts.usize); + Self::alloc_extern_static(this, name, val)?; + } + } + "windows" => { + // "_tls_used" + // This is some obscure hack that is part of the Windows TLS story. It's a `u8`. + let val = ImmTy::from_int(0, this.machine.layouts.u8); + Self::alloc_extern_static(this, "_tls_used", val)?; + } + _ => {} // No "extern statics" supported on this target + } + Ok(()) + } + + pub(crate) fn communicate(&self) -> bool { + self.isolated_op == IsolatedOp::Allow + } + + /// Check whether the stack frame that this `FrameInfo` refers to is part of a local crate. + pub(crate) fn is_local(&self, frame: &FrameInfo<'_>) -> bool { + let def_id = frame.instance.def_id(); + def_id.is_local() || self.local_crates.contains(&def_id.krate) + } +} + +/// A rustc InterpCx for Miri. +pub type MiriInterpCx<'mir, 'tcx> = InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>; + +/// A little trait that's useful to be inherited by extension traits. +pub trait MiriInterpCxExt<'mir, 'tcx> { + fn eval_context_ref<'a>(&'a self) -> &'a MiriInterpCx<'mir, 'tcx>; + fn eval_context_mut<'a>(&'a mut self) -> &'a mut MiriInterpCx<'mir, 'tcx>; +} +impl<'mir, 'tcx> MiriInterpCxExt<'mir, 'tcx> for MiriInterpCx<'mir, 'tcx> { + #[inline(always)] + fn eval_context_ref(&self) -> &MiriInterpCx<'mir, 'tcx> { + self + } + #[inline(always)] + fn eval_context_mut(&mut self) -> &mut MiriInterpCx<'mir, 'tcx> { + self + } +} + +/// Machine hook implementations. +impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> { + type MemoryKind = MiriMemoryKind; + type ExtraFnVal = Dlsym; + + type FrameExtra = FrameData<'tcx>; + type AllocExtra = AllocExtra; + + type Provenance = Provenance; + type ProvenanceExtra = ProvenanceExtra; + + type MemoryMap = MonoHashMap< + AllocId, + (MemoryKind, Allocation), + >; + + const GLOBAL_KIND: Option = Some(MiriMemoryKind::Global); + + const PANIC_ON_ALLOC_FAIL: bool = false; + + #[inline(always)] + fn enforce_alignment(ecx: &MiriInterpCx<'mir, 'tcx>) -> bool { + ecx.machine.check_alignment != AlignmentCheck::None + } + + #[inline(always)] + fn use_addr_for_alignment_check(ecx: &MiriInterpCx<'mir, 'tcx>) -> bool { + ecx.machine.check_alignment == AlignmentCheck::Int + } + + #[inline(always)] + fn enforce_validity(ecx: &MiriInterpCx<'mir, 'tcx>) -> bool { + ecx.machine.validate + } + + #[inline(always)] + fn enforce_abi(ecx: &MiriInterpCx<'mir, 'tcx>) -> bool { + ecx.machine.enforce_abi + } + + #[inline(always)] + fn checked_binop_checks_overflow(ecx: &MiriInterpCx<'mir, 'tcx>) -> bool { + ecx.tcx.sess.overflow_checks() + } + + #[inline(always)] + fn find_mir_or_eval_fn( + ecx: &mut MiriInterpCx<'mir, 'tcx>, + instance: ty::Instance<'tcx>, + abi: Abi, + args: &[OpTy<'tcx, Provenance>], + dest: &PlaceTy<'tcx, Provenance>, + ret: Option, + unwind: StackPopUnwind, + ) -> InterpResult<'tcx, Option<(&'mir mir::Body<'tcx>, ty::Instance<'tcx>)>> { + ecx.find_mir_or_eval_fn(instance, abi, args, dest, ret, unwind) + } + + #[inline(always)] + fn call_extra_fn( + ecx: &mut MiriInterpCx<'mir, 'tcx>, + fn_val: Dlsym, + abi: Abi, + args: &[OpTy<'tcx, Provenance>], + dest: &PlaceTy<'tcx, Provenance>, + ret: Option, + _unwind: StackPopUnwind, + ) -> InterpResult<'tcx> { + ecx.call_dlsym(fn_val, abi, args, dest, ret) + } + + #[inline(always)] + fn call_intrinsic( + ecx: &mut MiriInterpCx<'mir, 'tcx>, + instance: ty::Instance<'tcx>, + args: &[OpTy<'tcx, Provenance>], + dest: &PlaceTy<'tcx, Provenance>, + ret: Option, + unwind: StackPopUnwind, + ) -> InterpResult<'tcx> { + ecx.call_intrinsic(instance, args, dest, ret, unwind) + } + + #[inline(always)] + fn assert_panic( + ecx: &mut MiriInterpCx<'mir, 'tcx>, + msg: &mir::AssertMessage<'tcx>, + unwind: Option, + ) -> InterpResult<'tcx> { + ecx.assert_panic(msg, unwind) + } + + #[inline(always)] + fn abort(_ecx: &mut MiriInterpCx<'mir, 'tcx>, msg: String) -> InterpResult<'tcx, !> { + throw_machine_stop!(TerminationInfo::Abort(msg)) + } + + #[inline(always)] + fn binary_ptr_op( + ecx: &MiriInterpCx<'mir, 'tcx>, + bin_op: mir::BinOp, + left: &ImmTy<'tcx, Provenance>, + right: &ImmTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, (Scalar, bool, Ty<'tcx>)> { + ecx.binary_ptr_op(bin_op, left, right) + } + + fn thread_local_static_base_pointer( + ecx: &mut MiriInterpCx<'mir, 'tcx>, + def_id: DefId, + ) -> InterpResult<'tcx, Pointer> { + ecx.get_or_create_thread_local_alloc(def_id) + } + + fn extern_static_base_pointer( + ecx: &MiriInterpCx<'mir, 'tcx>, + def_id: DefId, + ) -> InterpResult<'tcx, Pointer> { + let link_name = ecx.item_link_name(def_id); + if let Some(&ptr) = ecx.machine.extern_statics.get(&link_name) { + // Various parts of the engine rely on `get_alloc_info` for size and alignment + // information. That uses the type information of this static. + // Make sure it matches the Miri allocation for this. + let Provenance::Concrete { alloc_id, .. } = ptr.provenance else { + panic!("extern_statics cannot contain wildcards") + }; + let (shim_size, shim_align, _kind) = ecx.get_alloc_info(alloc_id); + let extern_decl_layout = + ecx.tcx.layout_of(ty::ParamEnv::empty().and(ecx.tcx.type_of(def_id))).unwrap(); + if extern_decl_layout.size != shim_size || extern_decl_layout.align.abi != shim_align { + throw_unsup_format!( + "`extern` static `{name}` from crate `{krate}` has been declared \ + with a size of {decl_size} bytes and alignment of {decl_align} bytes, \ + but Miri emulates it via an extern static shim \ + with a size of {shim_size} bytes and alignment of {shim_align} bytes", + name = ecx.tcx.def_path_str(def_id), + krate = ecx.tcx.crate_name(def_id.krate), + decl_size = extern_decl_layout.size.bytes(), + decl_align = extern_decl_layout.align.abi.bytes(), + shim_size = shim_size.bytes(), + shim_align = shim_align.bytes(), + ) + } + Ok(ptr) + } else { + throw_unsup_format!( + "`extern` static `{name}` from crate `{krate}` is not supported by Miri", + name = ecx.tcx.def_path_str(def_id), + krate = ecx.tcx.crate_name(def_id.krate), + ) + } + } + + fn adjust_allocation<'b>( + ecx: &MiriInterpCx<'mir, 'tcx>, + id: AllocId, + alloc: Cow<'b, Allocation>, + kind: Option>, + ) -> InterpResult<'tcx, Cow<'b, Allocation>> { + let kind = kind.expect("we set our STATIC_KIND so this cannot be None"); + if ecx.machine.tracked_alloc_ids.contains(&id) { + ecx.emit_diagnostic(NonHaltingDiagnostic::CreatedAlloc( + id, + alloc.size(), + alloc.align, + kind, + )); + } + + let alloc = alloc.into_owned(); + let stacks = ecx.machine.stacked_borrows.as_ref().map(|stacked_borrows| { + Stacks::new_allocation( + id, + alloc.size(), + stacked_borrows, + kind, + ecx.machine.current_span(), + ) + }); + let race_alloc = ecx.machine.data_race.as_ref().map(|data_race| { + data_race::AllocExtra::new_allocation( + data_race, + &ecx.machine.threads, + alloc.size(), + kind, + ) + }); + let buffer_alloc = ecx.machine.weak_memory.then(weak_memory::AllocExtra::new_allocation); + let alloc: Allocation = alloc.adjust_from_tcx( + &ecx.tcx, + AllocExtra { + stacked_borrows: stacks.map(RefCell::new), + data_race: race_alloc, + weak_memory: buffer_alloc, + }, + |ptr| ecx.global_base_pointer(ptr), + )?; + Ok(Cow::Owned(alloc)) + } + + fn adjust_alloc_base_pointer( + ecx: &MiriInterpCx<'mir, 'tcx>, + ptr: Pointer, + ) -> Pointer { + if cfg!(debug_assertions) { + // The machine promises to never call us on thread-local or extern statics. + let alloc_id = ptr.provenance; + match ecx.tcx.try_get_global_alloc(alloc_id) { + Some(GlobalAlloc::Static(def_id)) if ecx.tcx.is_thread_local_static(def_id) => { + panic!("adjust_alloc_base_pointer called on thread-local static") + } + Some(GlobalAlloc::Static(def_id)) if ecx.tcx.is_foreign_item(def_id) => { + panic!("adjust_alloc_base_pointer called on extern static") + } + _ => {} + } + } + let absolute_addr = intptrcast::GlobalStateInner::rel_ptr_to_addr(ecx, ptr); + let sb_tag = if let Some(stacked_borrows) = &ecx.machine.stacked_borrows { + stacked_borrows.borrow_mut().base_ptr_tag(ptr.provenance, &ecx.machine) + } else { + // Value does not matter, SB is disabled + SbTag::default() + }; + Pointer::new( + Provenance::Concrete { alloc_id: ptr.provenance, sb: sb_tag }, + Size::from_bytes(absolute_addr), + ) + } + + #[inline(always)] + fn ptr_from_addr_cast( + ecx: &MiriInterpCx<'mir, 'tcx>, + addr: u64, + ) -> InterpResult<'tcx, Pointer>> { + intptrcast::GlobalStateInner::ptr_from_addr_cast(ecx, addr) + } + + fn expose_ptr( + ecx: &mut InterpCx<'mir, 'tcx, Self>, + ptr: Pointer, + ) -> InterpResult<'tcx> { + match ptr.provenance { + Provenance::Concrete { alloc_id, sb } => + intptrcast::GlobalStateInner::expose_ptr(ecx, alloc_id, sb), + Provenance::Wildcard => { + // No need to do anything for wildcard pointers as + // their provenances have already been previously exposed. + Ok(()) + } + } + } + + /// Convert a pointer with provenance into an allocation-offset pair, + /// or a `None` with an absolute address if that conversion is not possible. + fn ptr_get_alloc( + ecx: &MiriInterpCx<'mir, 'tcx>, + ptr: Pointer, + ) -> Option<(AllocId, Size, Self::ProvenanceExtra)> { + let rel = intptrcast::GlobalStateInner::abs_ptr_to_rel(ecx, ptr); + + rel.map(|(alloc_id, size)| { + let sb = match ptr.provenance { + Provenance::Concrete { sb, .. } => ProvenanceExtra::Concrete(sb), + Provenance::Wildcard => ProvenanceExtra::Wildcard, + }; + (alloc_id, size, sb) + }) + } + + #[inline(always)] + fn before_memory_read( + _tcx: TyCtxt<'tcx>, + machine: &Self, + alloc_extra: &AllocExtra, + (alloc_id, prov_extra): (AllocId, Self::ProvenanceExtra), + range: AllocRange, + ) -> InterpResult<'tcx> { + if let Some(data_race) = &alloc_extra.data_race { + data_race.read( + alloc_id, + range, + machine.data_race.as_ref().unwrap(), + &machine.threads, + )?; + } + if let Some(stacked_borrows) = &alloc_extra.stacked_borrows { + stacked_borrows.borrow_mut().before_memory_read( + alloc_id, + prov_extra, + range, + machine.stacked_borrows.as_ref().unwrap(), + machine.current_span(), + &machine.threads, + )?; + } + if let Some(weak_memory) = &alloc_extra.weak_memory { + weak_memory.memory_accessed(range, machine.data_race.as_ref().unwrap()); + } + Ok(()) + } + + #[inline(always)] + fn before_memory_write( + _tcx: TyCtxt<'tcx>, + machine: &mut Self, + alloc_extra: &mut AllocExtra, + (alloc_id, prov_extra): (AllocId, Self::ProvenanceExtra), + range: AllocRange, + ) -> InterpResult<'tcx> { + if let Some(data_race) = &mut alloc_extra.data_race { + data_race.write( + alloc_id, + range, + machine.data_race.as_mut().unwrap(), + &machine.threads, + )?; + } + if let Some(stacked_borrows) = &mut alloc_extra.stacked_borrows { + stacked_borrows.get_mut().before_memory_write( + alloc_id, + prov_extra, + range, + machine.stacked_borrows.as_ref().unwrap(), + machine.current_span(), + &machine.threads, + )?; + } + if let Some(weak_memory) = &alloc_extra.weak_memory { + weak_memory.memory_accessed(range, machine.data_race.as_ref().unwrap()); + } + Ok(()) + } + + #[inline(always)] + fn before_memory_deallocation( + _tcx: TyCtxt<'tcx>, + machine: &mut Self, + alloc_extra: &mut AllocExtra, + (alloc_id, prove_extra): (AllocId, Self::ProvenanceExtra), + range: AllocRange, + ) -> InterpResult<'tcx> { + if machine.tracked_alloc_ids.contains(&alloc_id) { + machine.emit_diagnostic(NonHaltingDiagnostic::FreedAlloc(alloc_id)); + } + if let Some(data_race) = &mut alloc_extra.data_race { + data_race.deallocate( + alloc_id, + range, + machine.data_race.as_mut().unwrap(), + &machine.threads, + )?; + } + if let Some(stacked_borrows) = &mut alloc_extra.stacked_borrows { + stacked_borrows.get_mut().before_memory_deallocation( + alloc_id, + prove_extra, + range, + machine.stacked_borrows.as_ref().unwrap(), + machine.current_span(), + &machine.threads, + ) + } else { + Ok(()) + } + } + + #[inline(always)] + fn retag( + ecx: &mut InterpCx<'mir, 'tcx, Self>, + kind: mir::RetagKind, + place: &PlaceTy<'tcx, Provenance>, + ) -> InterpResult<'tcx> { + if ecx.machine.stacked_borrows.is_some() { ecx.retag(kind, place) } else { Ok(()) } + } + + #[inline(always)] + fn init_frame_extra( + ecx: &mut InterpCx<'mir, 'tcx, Self>, + frame: Frame<'mir, 'tcx, Provenance>, + ) -> InterpResult<'tcx, Frame<'mir, 'tcx, Provenance, FrameData<'tcx>>> { + // Start recording our event before doing anything else + let timing = if let Some(profiler) = ecx.machine.profiler.as_ref() { + let fn_name = frame.instance.to_string(); + let entry = ecx.machine.string_cache.entry(fn_name.clone()); + let name = entry.or_insert_with(|| profiler.alloc_string(&*fn_name)); + + Some(profiler.start_recording_interval_event_detached( + *name, + measureme::EventId::from_label(*name), + ecx.get_active_thread().to_u32(), + )) + } else { + None + }; + + let stacked_borrows = ecx.machine.stacked_borrows.as_ref(); + + let extra = FrameData { + stacked_borrows: stacked_borrows.map(|sb| sb.borrow_mut().new_frame(&ecx.machine)), + catch_unwind: None, + timing, + }; + Ok(frame.with_extra(extra)) + } + + fn stack<'a>( + ecx: &'a InterpCx<'mir, 'tcx, Self>, + ) -> &'a [Frame<'mir, 'tcx, Self::Provenance, Self::FrameExtra>] { + ecx.active_thread_stack() + } + + fn stack_mut<'a>( + ecx: &'a mut InterpCx<'mir, 'tcx, Self>, + ) -> &'a mut Vec> { + ecx.active_thread_stack_mut() + } + + fn before_terminator(ecx: &mut InterpCx<'mir, 'tcx, Self>) -> InterpResult<'tcx> { + ecx.machine.basic_block_count += 1u64; // a u64 that is only incremented by 1 will "never" overflow + ecx.machine.since_gc += 1; + // Possibly report our progress. + if let Some(report_progress) = ecx.machine.report_progress { + if ecx.machine.basic_block_count % u64::from(report_progress) == 0 { + ecx.emit_diagnostic(NonHaltingDiagnostic::ProgressReport { + block_count: ecx.machine.basic_block_count, + }); + } + } + + // Search for SbTags to find all live pointers, then remove all other tags from borrow + // stacks. + // When debug assertions are enabled, run the GC as often as possible so that any cases + // where it mistakenly removes an important tag become visible. + if ecx.machine.gc_interval > 0 && ecx.machine.since_gc >= ecx.machine.gc_interval { + ecx.machine.since_gc = 0; + ecx.garbage_collect_tags()?; + } + + // These are our preemption points. + ecx.maybe_preempt_active_thread(); + + // Make sure some time passes. + ecx.machine.clock.tick(); + + Ok(()) + } + + #[inline(always)] + fn after_stack_push(ecx: &mut InterpCx<'mir, 'tcx, Self>) -> InterpResult<'tcx> { + if ecx.machine.stacked_borrows.is_some() { ecx.retag_return_place() } else { Ok(()) } + } + + #[inline(always)] + fn after_stack_pop( + ecx: &mut InterpCx<'mir, 'tcx, Self>, + mut frame: Frame<'mir, 'tcx, Provenance, FrameData<'tcx>>, + unwinding: bool, + ) -> InterpResult<'tcx, StackPopJump> { + let timing = frame.extra.timing.take(); + if let Some(stacked_borrows) = &ecx.machine.stacked_borrows { + stacked_borrows.borrow_mut().end_call(&frame.extra); + } + let res = ecx.handle_stack_pop_unwind(frame.extra, unwinding); + if let Some(profiler) = ecx.machine.profiler.as_ref() { + profiler.finish_recording_interval_event(timing.unwrap()); + } + res + } +} diff --git a/src/tools/miri/src/mono_hash_map.rs b/src/tools/miri/src/mono_hash_map.rs new file mode 100644 index 0000000000000..45057632df9b5 --- /dev/null +++ b/src/tools/miri/src/mono_hash_map.rs @@ -0,0 +1,110 @@ +//! This is a "monotonic `FxHashMap`": A `FxHashMap` that, when shared, can be pushed to but not +//! otherwise mutated. We also box items in the map. This means we can safely provide +//! shared references into existing items in the `FxHashMap`, because they will not be dropped +//! (from being removed) or moved (because they are boxed). +//! The API is is completely tailored to what `memory.rs` needs. It is still in +//! a separate file to minimize the amount of code that has to care about the unsafety. + +use std::borrow::Borrow; +use std::cell::RefCell; +use std::collections::hash_map::Entry; +use std::hash::Hash; + +use rustc_data_structures::fx::FxHashMap; + +use crate::AllocMap; + +#[derive(Debug, Clone)] +pub struct MonoHashMap(RefCell>>); + +impl MonoHashMap { + /// This function exists for priroda to be able to iterate over all evaluator memory. + /// + /// The function is somewhat roundabout with the closure argument because internally the + /// `MonoHashMap` uses a `RefCell`. When iterating over the `FxHashMap` inside the `RefCell`, + /// we need to keep a borrow to the `FxHashMap` inside the iterator. The borrow is only alive + /// as long as the `Ref` returned by `RefCell::borrow()` is alive. So we can't return the + /// iterator, as that would drop the `Ref`. We can't return both, as it's not possible in Rust + /// to have a struct/tuple with a field that refers to another field. + pub fn iter(&self, f: impl FnOnce(&mut dyn Iterator) -> T) -> T { + f(&mut self.0.borrow().iter().map(|(k, v)| (k, &**v))) + } +} + +impl Default for MonoHashMap { + fn default() -> Self { + MonoHashMap(RefCell::new(Default::default())) + } +} + +impl AllocMap for MonoHashMap { + #[inline(always)] + fn contains_key(&mut self, k: &Q) -> bool + where + K: Borrow, + { + self.0.get_mut().contains_key(k) + } + + #[inline(always)] + fn insert(&mut self, k: K, v: V) -> Option { + self.0.get_mut().insert(k, Box::new(v)).map(|x| *x) + } + + #[inline(always)] + fn remove(&mut self, k: &Q) -> Option + where + K: Borrow, + { + self.0.get_mut().remove(k).map(|x| *x) + } + + #[inline(always)] + fn filter_map_collect(&self, mut f: impl FnMut(&K, &V) -> Option) -> Vec { + self.0.borrow().iter().filter_map(move |(k, v)| f(k, v)).collect() + } + + /// The most interesting method: Providing a shared reference without + /// holding the `RefCell` open, and inserting new data if the key + /// is not used yet. + /// `vacant` is called if the key is not found in the map; + /// if it returns a reference, that is used directly, if it + /// returns owned data, that is put into the map and returned. + #[inline(always)] + fn get_or(&self, k: K, vacant: impl FnOnce() -> Result) -> Result<&V, E> { + // We cannot hold borrow_mut while calling `vacant`, since that might have to do lookups in this very map. + if let Some(v) = self.0.borrow().get(&k) { + let val: *const V = &**v; + // This is safe because `val` points into a `Box`, that we know will not move and + // will also not be dropped as long as the shared reference `self` is live. + return unsafe { Ok(&*val) }; + } + let new_val = Box::new(vacant()?); + let val: *const V = &**self.0.borrow_mut().try_insert(k, new_val).ok().unwrap(); + // This is safe because `val` points into a `Box`, that we know will not move and + // will also not be dropped as long as the shared reference `self` is live. + unsafe { Ok(&*val) } + } + + /// Read-only lookup (avoid read-acquiring the RefCell). + fn get(&self, k: K) -> Option<&V> { + let val: *const V = match self.0.borrow().get(&k) { + Some(v) => &**v, + None => return None, + }; + // This is safe because `val` points into a `Box`, that we know will not move and + // will also not be dropped as long as the shared reference `self` is live. + unsafe { Some(&*val) } + } + + #[inline(always)] + fn get_mut_or(&mut self, k: K, vacant: impl FnOnce() -> Result) -> Result<&mut V, E> { + match self.0.get_mut().entry(k) { + Entry::Occupied(e) => Ok(e.into_mut()), + Entry::Vacant(e) => { + let v = vacant()?; + Ok(e.insert(Box::new(v))) + } + } + } +} diff --git a/src/tools/miri/src/operator.rs b/src/tools/miri/src/operator.rs new file mode 100644 index 0000000000000..a0ef7fcad16dc --- /dev/null +++ b/src/tools/miri/src/operator.rs @@ -0,0 +1,90 @@ +use log::trace; + +use rustc_middle::{mir, ty::Ty}; +use rustc_target::abi::Size; + +use crate::*; + +pub trait EvalContextExt<'tcx> { + fn binary_ptr_op( + &self, + bin_op: mir::BinOp, + left: &ImmTy<'tcx, Provenance>, + right: &ImmTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, (Scalar, bool, Ty<'tcx>)>; +} + +impl<'mir, 'tcx> EvalContextExt<'tcx> for super::MiriInterpCx<'mir, 'tcx> { + fn binary_ptr_op( + &self, + bin_op: mir::BinOp, + left: &ImmTy<'tcx, Provenance>, + right: &ImmTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, (Scalar, bool, Ty<'tcx>)> { + use rustc_middle::mir::BinOp::*; + + trace!("ptr_op: {:?} {:?} {:?}", *left, bin_op, *right); + + Ok(match bin_op { + Eq | Ne | Lt | Le | Gt | Ge => { + assert_eq!(left.layout.abi, right.layout.abi); // types an differ, e.g. fn ptrs with different `for` + let size = self.pointer_size(); + // Just compare the bits. ScalarPairs are compared lexicographically. + // We thus always compare pairs and simply fill scalars up with 0. + let left = match **left { + Immediate::Scalar(l) => (l.to_bits(size)?, 0), + Immediate::ScalarPair(l1, l2) => (l1.to_bits(size)?, l2.to_bits(size)?), + Immediate::Uninit => panic!("we should never see uninit data here"), + }; + let right = match **right { + Immediate::Scalar(r) => (r.to_bits(size)?, 0), + Immediate::ScalarPair(r1, r2) => (r1.to_bits(size)?, r2.to_bits(size)?), + Immediate::Uninit => panic!("we should never see uninit data here"), + }; + let res = match bin_op { + Eq => left == right, + Ne => left != right, + Lt => left < right, + Le => left <= right, + Gt => left > right, + Ge => left >= right, + _ => bug!(), + }; + (Scalar::from_bool(res), false, self.tcx.types.bool) + } + + Offset => { + assert!(left.layout.ty.is_unsafe_ptr()); + let ptr = left.to_scalar().to_pointer(self)?; + let offset = right.to_scalar().to_machine_isize(self)?; + + let pointee_ty = + left.layout.ty.builtin_deref(true).expect("Offset called on non-ptr type").ty; + let ptr = self.ptr_offset_inbounds(ptr, pointee_ty, offset)?; + (Scalar::from_maybe_pointer(ptr, self), false, left.layout.ty) + } + + // Some more operations are possible with atomics. + // The return value always has the provenance of the *left* operand. + Add | Sub | BitOr | BitAnd | BitXor => { + assert!(left.layout.ty.is_unsafe_ptr()); + assert!(right.layout.ty.is_unsafe_ptr()); + let ptr = left.to_scalar().to_pointer(self)?; + // We do the actual operation with usize-typed scalars. + let left = ImmTy::from_uint(ptr.addr().bytes(), self.machine.layouts.usize); + let right = ImmTy::from_uint( + right.to_scalar().to_machine_usize(self)?, + self.machine.layouts.usize, + ); + let (result, overflowing, _ty) = + self.overflowing_binary_op(bin_op, &left, &right)?; + // Construct a new pointer with the provenance of `ptr` (the LHS). + let result_ptr = + Pointer::new(ptr.provenance, Size::from_bytes(result.to_machine_usize(self)?)); + (Scalar::from_maybe_pointer(result_ptr, self), overflowing, left.layout.ty) + } + + _ => span_bug!(self.cur_span(), "Invalid operator on pointers: {:?}", bin_op), + }) + } +} diff --git a/src/tools/miri/src/range_map.rs b/src/tools/miri/src/range_map.rs new file mode 100644 index 0000000000000..c77ea63b0873f --- /dev/null +++ b/src/tools/miri/src/range_map.rs @@ -0,0 +1,300 @@ +//! Implements a map from integer indices to data. +//! Rather than storing data for every index, internally, this maps entire ranges to the data. +//! To this end, the APIs all work on ranges, not on individual integers. Ranges are split as +//! necessary (e.g., when [0,5) is first associated with X, and then [1,2) is mutated). +//! Users must not depend on whether a range is coalesced or not, even though this is observable +//! via the iteration APIs. + +use std::ops; + +use rustc_target::abi::Size; + +#[derive(Clone, Debug)] +struct Elem { + /// The range covered by this element; never empty. + range: ops::Range, + /// The data stored for this element. + data: T, +} +#[derive(Clone, Debug)] +pub struct RangeMap { + v: Vec>, +} + +impl RangeMap { + /// Creates a new `RangeMap` for the given size, and with the given initial value used for + /// the entire range. + #[inline(always)] + pub fn new(size: Size, init: T) -> RangeMap { + let size = size.bytes(); + let mut map = RangeMap { v: Vec::new() }; + if size > 0 { + map.v.push(Elem { range: 0..size, data: init }); + } + map + } + + /// Finds the index containing the given offset. + fn find_offset(&self, offset: u64) -> usize { + // We do a binary search. + let mut left = 0usize; // inclusive + let mut right = self.v.len(); // exclusive + loop { + debug_assert!(left < right, "find_offset: offset {} is out-of-bounds", offset); + let candidate = left.checked_add(right).unwrap() / 2; + let elem = &self.v[candidate]; + if offset < elem.range.start { + // We are too far right (offset is further left). + debug_assert!(candidate < right); // we are making progress + right = candidate; + } else if offset >= elem.range.end { + // We are too far left (offset is further right). + debug_assert!(candidate >= left); // we are making progress + left = candidate + 1; + } else { + // This is it! + return candidate; + } + } + } + + /// Provides read-only iteration over everything in the given range. This does + /// *not* split items if they overlap with the edges. Do not use this to mutate + /// through interior mutability. + /// + /// The iterator also provides the offset of the given element. + pub fn iter(&self, offset: Size, len: Size) -> impl Iterator { + let offset = offset.bytes(); + let len = len.bytes(); + // Compute a slice starting with the elements we care about. + let slice: &[Elem] = if len == 0 { + // We just need any empty iterator. We don't even want to + // yield the element that surrounds this position. + &[] + } else { + let first_idx = self.find_offset(offset); + &self.v[first_idx..] + }; + // The first offset that is not included any more. + let end = offset + len; + assert!( + end <= self.v.last().unwrap().range.end, + "iterating beyond the bounds of this RangeMap" + ); + slice + .iter() + .take_while(move |elem| elem.range.start < end) + .map(|elem| (Size::from_bytes(elem.range.start), &elem.data)) + } + + pub fn iter_mut_all(&mut self) -> impl Iterator { + self.v.iter_mut().map(|elem| &mut elem.data) + } + + // Splits the element situated at the given `index`, such that the 2nd one starts at offset + // `split_offset`. Do nothing if the element already starts there. + // Returns whether a split was necessary. + fn split_index(&mut self, index: usize, split_offset: u64) -> bool + where + T: Clone, + { + let elem = &mut self.v[index]; + if split_offset == elem.range.start || split_offset == elem.range.end { + // Nothing to do. + return false; + } + debug_assert!( + elem.range.contains(&split_offset), + "the `split_offset` is not in the element to be split" + ); + + // Now we really have to split. Reduce length of first element. + let second_range = split_offset..elem.range.end; + elem.range.end = split_offset; + // Copy the data, and insert second element. + let second = Elem { range: second_range, data: elem.data.clone() }; + self.v.insert(index + 1, second); + true + } + + /// Provides mutable iteration over everything in the given range. As a side-effect, + /// this will split entries in the map that are only partially hit by the given range, + /// to make sure that when they are mutated, the effect is constrained to the given range. + /// Moreover, this will opportunistically merge neighbouring equal blocks. + /// + /// The iterator also provides the offset of the given element. + pub fn iter_mut(&mut self, offset: Size, len: Size) -> impl Iterator + where + T: Clone + PartialEq, + { + let offset = offset.bytes(); + let len = len.bytes(); + // Compute a slice containing exactly the elements we care about + let slice: &mut [Elem] = if len == 0 { + // We just need any empty iterator. We don't even want to + // yield the element that surrounds this position, nor do + // any splitting. + &mut [] + } else { + // Make sure we got a clear beginning + let mut first_idx = self.find_offset(offset); + if self.split_index(first_idx, offset) { + // The newly created 2nd element is ours + first_idx += 1; + } + // No more mutation. + let first_idx = first_idx; + // Find our end. Linear scan, but that's ok because the iteration + // is doing the same linear scan anyway -- no increase in complexity. + // We combine this scan with a scan for duplicates that we can merge, to reduce + // the number of elements. + // We stop searching after the first "block" of size 1, to avoid spending excessive + // amounts of time on the merging. + let mut equal_since_idx = first_idx; + // Once we see too many non-mergeable blocks, we stop. + // The initial value is chosen via... magic. Benchmarking and magic. + let mut successful_merge_count = 3usize; + // When the loop is done, this is the first excluded element. + let mut end_idx = first_idx; + loop { + // Compute if `end` is the last element we need to look at. + let done = self.v[end_idx].range.end >= offset + len; + // We definitely need to include `end`, so move the index. + end_idx += 1; + debug_assert!( + done || end_idx < self.v.len(), + "iter_mut: end-offset {} is out-of-bounds", + offset + len + ); + // see if we want to merge everything in `equal_since..end` (exclusive at the end!) + if successful_merge_count > 0 { + if done || self.v[end_idx].data != self.v[equal_since_idx].data { + // Everything in `equal_since..end` was equal. Make them just one element covering + // the entire range. + let removed_elems = end_idx - equal_since_idx - 1; // number of elements that we would remove + if removed_elems > 0 { + // Adjust the range of the first element to cover all of them. + let equal_until = self.v[end_idx - 1].range.end; // end of range of last of the equal elements + self.v[equal_since_idx].range.end = equal_until; + // Delete the rest of them. + self.v.splice(equal_since_idx + 1..end_idx, std::iter::empty()); + // Adjust `end_idx` because we made the list shorter. + end_idx -= removed_elems; + // Adjust the count for the cutoff. + successful_merge_count += removed_elems; + } else { + // Adjust the count for the cutoff. + successful_merge_count -= 1; + } + // Go on scanning for the next block starting here. + equal_since_idx = end_idx; + } + } + // Leave loop if this is the last element. + if done { + break; + } + } + // Move to last included instead of first excluded index. + let end_idx = end_idx - 1; + // We need to split the end as well. Even if this performs a + // split, we don't have to adjust our index as we only care about + // the first part of the split. + self.split_index(end_idx, offset + len); + // Now we yield the slice. `end` is inclusive. + &mut self.v[first_idx..=end_idx] + }; + slice.iter_mut().map(|elem| (Size::from_bytes(elem.range.start), &mut elem.data)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Query the map at every offset in the range and collect the results. + fn to_vec(map: &RangeMap, offset: u64, len: u64) -> Vec { + (offset..offset + len) + .into_iter() + .map(|i| { + map.iter(Size::from_bytes(i), Size::from_bytes(1)).next().map(|(_, &t)| t).unwrap() + }) + .collect() + } + + #[test] + fn basic_insert() { + let mut map = RangeMap::::new(Size::from_bytes(20), -1); + // Insert. + for (_, x) in map.iter_mut(Size::from_bytes(10), Size::from_bytes(1)) { + *x = 42; + } + // Check. + assert_eq!(to_vec(&map, 10, 1), vec![42]); + assert_eq!(map.v.len(), 3); + + // Insert with size 0. + for (_, x) in map.iter_mut(Size::from_bytes(10), Size::from_bytes(0)) { + *x = 19; + } + for (_, x) in map.iter_mut(Size::from_bytes(11), Size::from_bytes(0)) { + *x = 19; + } + assert_eq!(to_vec(&map, 10, 2), vec![42, -1]); + assert_eq!(map.v.len(), 3); + } + + #[test] + fn gaps() { + let mut map = RangeMap::::new(Size::from_bytes(20), -1); + for (_, x) in map.iter_mut(Size::from_bytes(11), Size::from_bytes(1)) { + *x = 42; + } + for (_, x) in map.iter_mut(Size::from_bytes(15), Size::from_bytes(1)) { + *x = 43; + } + assert_eq!(map.v.len(), 5); + assert_eq!(to_vec(&map, 10, 10), vec![-1, 42, -1, -1, -1, 43, -1, -1, -1, -1]); + + for (_, x) in map.iter_mut(Size::from_bytes(10), Size::from_bytes(10)) { + if *x < 42 { + *x = 23; + } + } + assert_eq!(map.v.len(), 6); + assert_eq!(to_vec(&map, 10, 10), vec![23, 42, 23, 23, 23, 43, 23, 23, 23, 23]); + assert_eq!(to_vec(&map, 13, 5), vec![23, 23, 43, 23, 23]); + + for (_, x) in map.iter_mut(Size::from_bytes(15), Size::from_bytes(5)) { + *x = 19; + } + assert_eq!(map.v.len(), 6); + assert_eq!(to_vec(&map, 10, 10), vec![23, 42, 23, 23, 23, 19, 19, 19, 19, 19]); + // Should be seeing two blocks with 19. + assert_eq!( + map.iter(Size::from_bytes(15), Size::from_bytes(2)) + .map(|(_, &t)| t) + .collect::>(), + vec![19, 19] + ); + + // A NOP `iter_mut` should trigger merging. + for _ in map.iter_mut(Size::from_bytes(15), Size::from_bytes(5)) {} + assert_eq!(map.v.len(), 5); + assert_eq!(to_vec(&map, 10, 10), vec![23, 42, 23, 23, 23, 19, 19, 19, 19, 19]); + } + + #[test] + #[should_panic] + fn out_of_range_iter_mut() { + let mut map = RangeMap::::new(Size::from_bytes(20), -1); + let _ = map.iter_mut(Size::from_bytes(11), Size::from_bytes(11)); + } + + #[test] + #[should_panic] + fn out_of_range_iter() { + let map = RangeMap::::new(Size::from_bytes(20), -1); + let _ = map.iter(Size::from_bytes(11), Size::from_bytes(11)); + } +} diff --git a/src/tools/miri/src/shims/backtrace.rs b/src/tools/miri/src/shims/backtrace.rs new file mode 100644 index 0000000000000..15987eee537fd --- /dev/null +++ b/src/tools/miri/src/shims/backtrace.rs @@ -0,0 +1,254 @@ +use crate::*; +use rustc_ast::ast::Mutability; +use rustc_middle::ty::layout::LayoutOf as _; +use rustc_middle::ty::{self, Instance}; +use rustc_span::{BytePos, Loc, Symbol}; +use rustc_target::{abi::Size, spec::abi::Abi}; + +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + fn handle_miri_backtrace_size( + &mut self, + abi: Abi, + link_name: Symbol, + args: &[OpTy<'tcx, Provenance>], + dest: &PlaceTy<'tcx, Provenance>, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + let [flags] = this.check_shim(abi, Abi::Rust, link_name, args)?; + + let flags = this.read_scalar(flags)?.to_u64()?; + if flags != 0 { + throw_unsup_format!("unknown `miri_backtrace_size` flags {}", flags); + } + + let frame_count = this.active_thread_stack().len(); + + this.write_scalar(Scalar::from_machine_usize(frame_count.try_into().unwrap(), this), dest) + } + + fn handle_miri_get_backtrace( + &mut self, + abi: Abi, + link_name: Symbol, + args: &[OpTy<'tcx, Provenance>], + dest: &PlaceTy<'tcx, Provenance>, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + let tcx = this.tcx; + + let flags = if let Some(flags_op) = args.get(0) { + this.read_scalar(flags_op)?.to_u64()? + } else { + throw_ub_format!("expected at least 1 argument") + }; + + let mut data = Vec::new(); + for frame in this.active_thread_stack().iter().rev() { + let mut span = frame.current_span(); + // Match the behavior of runtime backtrace spans + // by using a non-macro span in our backtrace. See `FunctionCx::debug_loc`. + if span.from_expansion() && !tcx.sess.opts.unstable_opts.debug_macros { + span = rustc_span::hygiene::walk_chain(span, frame.body.span.ctxt()) + } + data.push((frame.instance, span.lo())); + } + + let ptrs: Vec<_> = data + .into_iter() + .map(|(instance, pos)| { + // We represent a frame pointer by using the `span.lo` value + // as an offset into the function's allocation. This gives us an + // opaque pointer that we can return to user code, and allows us + // to reconstruct the needed frame information in `handle_miri_resolve_frame`. + // Note that we never actually read or write anything from/to this pointer - + // all of the data is represented by the pointer value itself. + let fn_ptr = this.create_fn_alloc_ptr(FnVal::Instance(instance)); + fn_ptr.wrapping_offset(Size::from_bytes(pos.0), this) + }) + .collect(); + + let len: u64 = ptrs.len().try_into().unwrap(); + + let ptr_ty = this.machine.layouts.mut_raw_ptr.ty; + let array_layout = this.layout_of(tcx.mk_array(ptr_ty, len)).unwrap(); + + match flags { + // storage for pointers is allocated by miri + // deallocating the slice is undefined behavior with a custom global allocator + 0 => { + let [_flags] = this.check_shim(abi, Abi::Rust, link_name, args)?; + + let alloc = this.allocate(array_layout, MiriMemoryKind::Rust.into())?; + + // Write pointers into array + for (i, ptr) in ptrs.into_iter().enumerate() { + let place = this.mplace_index(&alloc, i as u64)?; + + this.write_pointer(ptr, &place.into())?; + } + + this.write_immediate( + Immediate::new_slice(Scalar::from_maybe_pointer(alloc.ptr, this), len, this), + dest, + )?; + } + // storage for pointers is allocated by the caller + 1 => { + let [_flags, buf] = this.check_shim(abi, Abi::Rust, link_name, args)?; + + let buf_place = this.deref_operand(buf)?; + + let ptr_layout = this.layout_of(ptr_ty)?; + + for (i, ptr) in ptrs.into_iter().enumerate() { + let offset = ptr_layout.size * i.try_into().unwrap(); + + let op_place = buf_place.offset(offset, ptr_layout, this)?; + + this.write_pointer(ptr, &op_place.into())?; + } + } + _ => throw_unsup_format!("unknown `miri_get_backtrace` flags {}", flags), + }; + + Ok(()) + } + + fn resolve_frame_pointer( + &mut self, + ptr: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, (Instance<'tcx>, Loc, String, String)> { + let this = self.eval_context_mut(); + + let ptr = this.read_pointer(ptr)?; + // Take apart the pointer, we need its pieces. The offset encodes the span. + let (alloc_id, offset, _prov) = this.ptr_get_alloc_id(ptr)?; + + // This has to be an actual global fn ptr, not a dlsym function. + let fn_instance = if let Some(GlobalAlloc::Function(instance)) = + this.tcx.try_get_global_alloc(alloc_id) + { + instance + } else { + throw_ub_format!("expected static function pointer, found {:?}", ptr); + }; + + let lo = + this.tcx.sess.source_map().lookup_char_pos(BytePos(offset.bytes().try_into().unwrap())); + + let name = fn_instance.to_string(); + let filename = lo.file.name.prefer_remapped().to_string(); + + Ok((fn_instance, lo, name, filename)) + } + + fn handle_miri_resolve_frame( + &mut self, + abi: Abi, + link_name: Symbol, + args: &[OpTy<'tcx, Provenance>], + dest: &PlaceTy<'tcx, Provenance>, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + let [ptr, flags] = this.check_shim(abi, Abi::Rust, link_name, args)?; + + let flags = this.read_scalar(flags)?.to_u64()?; + + let (fn_instance, lo, name, filename) = this.resolve_frame_pointer(ptr)?; + + // Reconstruct the original function pointer, + // which we pass to user code. + let fn_ptr = this.create_fn_alloc_ptr(FnVal::Instance(fn_instance)); + + let num_fields = dest.layout.fields.count(); + + if !(4..=5).contains(&num_fields) { + // Always mention 5 fields, since the 4-field struct + // is deprecated and slated for removal. + throw_ub_format!( + "bad declaration of miri_resolve_frame - should return a struct with 5 fields" + ); + } + + // `u32` is not enough to fit line/colno, which can be `usize`. It seems unlikely that a + // file would have more than 2^32 lines or columns, but whatever, just default to 0. + let lineno: u32 = u32::try_from(lo.line).unwrap_or(0); + // `lo.col` is 0-based - add 1 to make it 1-based for the caller. + let colno: u32 = u32::try_from(lo.col.0.saturating_add(1)).unwrap_or(0); + + let dest = this.force_allocation(dest)?; + if let ty::Adt(adt, _) = dest.layout.ty.kind() { + if !adt.repr().c() { + throw_ub_format!( + "miri_resolve_frame must be declared with a `#[repr(C)]` return type" + ); + } + } + + match flags { + 0 => { + // These are "mutable" allocations as we consider them to be owned by the callee. + let name_alloc = + this.allocate_str(&name, MiriMemoryKind::Rust.into(), Mutability::Mut); + let filename_alloc = + this.allocate_str(&filename, MiriMemoryKind::Rust.into(), Mutability::Mut); + + this.write_immediate( + name_alloc.to_ref(this), + &this.mplace_field(&dest, 0)?.into(), + )?; + this.write_immediate( + filename_alloc.to_ref(this), + &this.mplace_field(&dest, 1)?.into(), + )?; + } + 1 => { + this.write_scalar( + Scalar::from_machine_usize(name.len().try_into().unwrap(), this), + &this.mplace_field(&dest, 0)?.into(), + )?; + this.write_scalar( + Scalar::from_machine_usize(filename.len().try_into().unwrap(), this), + &this.mplace_field(&dest, 1)?.into(), + )?; + } + _ => throw_unsup_format!("unknown `miri_resolve_frame` flags {}", flags), + } + + this.write_scalar(Scalar::from_u32(lineno), &this.mplace_field(&dest, 2)?.into())?; + this.write_scalar(Scalar::from_u32(colno), &this.mplace_field(&dest, 3)?.into())?; + + // Support a 4-field struct for now - this is deprecated + // and slated for removal. + if num_fields == 5 { + this.write_pointer(fn_ptr, &this.mplace_field(&dest, 4)?.into())?; + } + + Ok(()) + } + + fn handle_miri_resolve_frame_names( + &mut self, + abi: Abi, + link_name: Symbol, + args: &[OpTy<'tcx, Provenance>], + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + + let [ptr, flags, name_ptr, filename_ptr] = + this.check_shim(abi, Abi::Rust, link_name, args)?; + + let flags = this.read_scalar(flags)?.to_u64()?; + if flags != 0 { + throw_unsup_format!("unknown `miri_resolve_frame_names` flags {}", flags); + } + + let (_, _, name, filename) = this.resolve_frame_pointer(ptr)?; + + this.write_bytes_ptr(this.read_pointer(name_ptr)?, name.bytes())?; + this.write_bytes_ptr(this.read_pointer(filename_ptr)?, filename.bytes())?; + + Ok(()) + } +} diff --git a/src/tools/miri/src/shims/dlsym.rs b/src/tools/miri/src/shims/dlsym.rs new file mode 100644 index 0000000000000..8bf6d24f85f31 --- /dev/null +++ b/src/tools/miri/src/shims/dlsym.rs @@ -0,0 +1,48 @@ +use rustc_middle::mir; +use rustc_target::spec::abi::Abi; + +use crate::helpers::target_os_is_unix; +use crate::*; +use shims::unix::dlsym as unix; +use shims::windows::dlsym as windows; + +#[derive(Debug, Copy, Clone)] +#[allow(non_camel_case_types)] +pub enum Dlsym { + Posix(unix::Dlsym), + Windows(windows::Dlsym), +} + +impl Dlsym { + // Returns an error for unsupported symbols, and None if this symbol + // should become a NULL pointer (pretend it does not exist). + pub fn from_str<'tcx>(name: &[u8], target_os: &str) -> InterpResult<'tcx, Option> { + let name = &*String::from_utf8_lossy(name); + Ok(match target_os { + target if target_os_is_unix(target) => + unix::Dlsym::from_str(name, target)?.map(Dlsym::Posix), + "windows" => windows::Dlsym::from_str(name)?.map(Dlsym::Windows), + os => bug!("dlsym not implemented for target_os {}", os), + }) + } +} + +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + fn call_dlsym( + &mut self, + dlsym: Dlsym, + abi: Abi, + args: &[OpTy<'tcx, Provenance>], + dest: &PlaceTy<'tcx, Provenance>, + ret: Option, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + match dlsym { + Dlsym::Posix(dlsym) => + unix::EvalContextExt::call_dlsym(this, dlsym, abi, args, dest, ret), + Dlsym::Windows(dlsym) => + windows::EvalContextExt::call_dlsym(this, dlsym, abi, args, dest, ret), + } + } +} diff --git a/src/tools/miri/src/shims/env.rs b/src/tools/miri/src/shims/env.rs new file mode 100644 index 0000000000000..95051c998e5fd --- /dev/null +++ b/src/tools/miri/src/shims/env.rs @@ -0,0 +1,469 @@ +use std::env; +use std::ffi::{OsStr, OsString}; +use std::io::ErrorKind; +use std::mem; + +use rustc_const_eval::interpret::Pointer; +use rustc_data_structures::fx::FxHashMap; +use rustc_middle::ty::layout::LayoutOf; +use rustc_target::abi::Size; + +use crate::helpers::target_os_is_unix; +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() + } +} + +#[derive(Default)] +pub struct EnvVars<'tcx> { + /// Stores pointers to the environment variables. These variables must be stored as + /// null-terminated target strings (c_str or wide_str) with the `"{name}={value}"` format. + map: FxHashMap>>, + + /// Place where the `environ` static is stored. Lazily initialized, but then never changes. + pub(crate) environ: Option>, +} + +impl<'tcx> EnvVars<'tcx> { + pub(crate) fn init<'mir>( + ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>, + config: &MiriConfig, + ) -> InterpResult<'tcx> { + let target_os = ecx.tcx.sess.target.os.as_ref(); + + // Skip the loop entirely if we don't want to forward anything. + if ecx.machine.communicate() || !config.forwarded_env_vars.is_empty() { + for (name, value) in &config.env { + let forward = ecx.machine.communicate() + || config.forwarded_env_vars.iter().any(|v| **v == *name); + if forward { + let var_ptr = match target_os { + target if target_os_is_unix(target) => + alloc_env_var_as_c_str(name.as_ref(), value.as_ref(), ecx)?, + "windows" => alloc_env_var_as_wide_str(name.as_ref(), value.as_ref(), ecx)?, + unsupported => + throw_unsup_format!( + "environment support for target OS `{}` not yet available", + unsupported + ), + }; + ecx.machine.env_vars.map.insert(name.clone(), var_ptr); + } + } + } + ecx.update_environ() + } + + pub(crate) fn cleanup<'mir>( + ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>, + ) -> InterpResult<'tcx> { + // Deallocate individual env vars. + let env_vars = mem::take(&mut ecx.machine.env_vars.map); + for (_name, ptr) in env_vars { + ecx.deallocate_ptr(ptr, None, MiriMemoryKind::Runtime.into())?; + } + // Deallocate environ var list. + let environ = ecx.machine.env_vars.environ.unwrap(); + let old_vars_ptr = ecx.read_pointer(&environ.into())?; + ecx.deallocate_ptr(old_vars_ptr, None, MiriMemoryKind::Runtime.into())?; + Ok(()) + } +} + +fn alloc_env_var_as_c_str<'mir, 'tcx>( + name: &OsStr, + value: &OsStr, + ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>, +) -> InterpResult<'tcx, Pointer>> { + let mut name_osstring = name.to_os_string(); + name_osstring.push("="); + name_osstring.push(value); + ecx.alloc_os_str_as_c_str(name_osstring.as_os_str(), MiriMemoryKind::Runtime.into()) +} + +fn alloc_env_var_as_wide_str<'mir, 'tcx>( + name: &OsStr, + value: &OsStr, + ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>, +) -> InterpResult<'tcx, Pointer>> { + let mut name_osstring = name.to_os_string(); + name_osstring.push("="); + name_osstring.push(value); + ecx.alloc_os_str_as_wide_str(name_osstring.as_os_str(), MiriMemoryKind::Runtime.into()) +} + +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + fn getenv( + &mut self, + name_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, Pointer>> { + let this = self.eval_context_mut(); + this.assert_target_os_is_unix("getenv"); + + let name_ptr = this.read_pointer(name_op)?; + let name = this.read_os_str_from_c_str(name_ptr)?; + Ok(match this.machine.env_vars.map.get(name) { + Some(var_ptr) => { + // The offset is used to strip the "{name}=" part of the string. + var_ptr.offset( + Size::from_bytes(u64::try_from(name.len()).unwrap().checked_add(1).unwrap()), + this, + )? + } + None => Pointer::null(), + }) + } + + #[allow(non_snake_case)] + fn GetEnvironmentVariableW( + &mut self, + name_op: &OpTy<'tcx, Provenance>, // LPCWSTR + buf_op: &OpTy<'tcx, Provenance>, // LPWSTR + size_op: &OpTy<'tcx, Provenance>, // DWORD + ) -> InterpResult<'tcx, u32> { + // ^ Returns DWORD (u32 on Windows) + + let this = self.eval_context_mut(); + this.assert_target_os("windows", "GetEnvironmentVariableW"); + + 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) => { + // The offset is used to strip the "{name}=" part of the string. + #[rustfmt::skip] + let name_offset_bytes = u64::try_from(name.len()).unwrap() + .checked_add(1).unwrap() + .checked_mul(2).unwrap(); + let var_ptr = var_ptr.offset(Size::from_bytes(name_offset_bytes), this)?; + let var = this.read_os_str_from_wide_str(var_ptr)?; + + let buf_ptr = this.read_pointer(buf_op)?; + // `buf_size` represents the size in characters. + let buf_size = u64::from(this.read_scalar(size_op)?.to_u32()?); + windows_check_buffer_size(this.write_os_str_to_wide_str(&var, buf_ptr, buf_size)?) + } + None => { + let envvar_not_found = this.eval_windows("c", "ERROR_ENVVAR_NOT_FOUND")?; + this.set_last_error(envvar_not_found)?; + 0 // return zero upon failure + } + }) + } + + #[allow(non_snake_case)] + fn GetEnvironmentStringsW(&mut self) -> InterpResult<'tcx, Pointer>> { + let this = self.eval_context_mut(); + this.assert_target_os("windows", "GetEnvironmentStringsW"); + + // Info on layout of environment blocks in Windows: + // https://docs.microsoft.com/en-us/windows/win32/procthread/environment-variables + let mut env_vars = std::ffi::OsString::new(); + for &item in this.machine.env_vars.map.values() { + let env_var = this.read_os_str_from_wide_str(item)?; + env_vars.push(env_var); + env_vars.push("\0"); + } + // Allocate environment block & Store environment variables to environment block. + // Final null terminator(block terminator) is added by `alloc_os_str_to_wide_str`. + let envblock_ptr = + this.alloc_os_str_as_wide_str(&env_vars, MiriMemoryKind::Runtime.into())?; + // If the function succeeds, the return value is a pointer to the environment block of the current process. + Ok(envblock_ptr) + } + + #[allow(non_snake_case)] + fn FreeEnvironmentStringsW( + &mut self, + env_block_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + this.assert_target_os("windows", "FreeEnvironmentStringsW"); + + let env_block_ptr = this.read_pointer(env_block_op)?; + let result = this.deallocate_ptr(env_block_ptr, None, MiriMemoryKind::Runtime.into()); + // If the function succeeds, the return value is nonzero. + Ok(i32::from(result.is_ok())) + } + + fn setenv( + &mut self, + name_op: &OpTy<'tcx, Provenance>, + value_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + this.assert_target_os_is_unix("setenv"); + + let name_ptr = this.read_pointer(name_op)?; + let value_ptr = this.read_pointer(value_op)?; + + let mut new = None; + if !this.ptr_is_null(name_ptr)? { + let name = this.read_os_str_from_c_str(name_ptr)?; + if !name.is_empty() && !name.to_string_lossy().contains('=') { + let value = this.read_os_str_from_c_str(value_ptr)?; + new = Some((name.to_owned(), value.to_owned())); + } + } + if let Some((name, value)) = new { + let var_ptr = alloc_env_var_as_c_str(&name, &value, this)?; + if let Some(var) = this.machine.env_vars.map.insert(name, var_ptr) { + this.deallocate_ptr(var, None, MiriMemoryKind::Runtime.into())?; + } + this.update_environ()?; + Ok(0) // return zero on success + } else { + // name argument is a null pointer, points to an empty string, or points to a string containing an '=' character. + let einval = this.eval_libc("EINVAL")?; + this.set_last_error(einval)?; + Ok(-1) + } + } + + #[allow(non_snake_case)] + fn SetEnvironmentVariableW( + &mut self, + name_op: &OpTy<'tcx, Provenance>, // LPCWSTR + value_op: &OpTy<'tcx, Provenance>, // LPCWSTR + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + this.assert_target_os("windows", "SetEnvironmentVariableW"); + + let name_ptr = this.read_pointer(name_op)?; + let value_ptr = this.read_pointer(value_op)?; + + if this.ptr_is_null(name_ptr)? { + // ERROR CODE is not clearly explained in docs.. For now, throw UB instead. + throw_ub_format!("pointer to environment variable name is NULL"); + } + + let name = this.read_os_str_from_wide_str(name_ptr)?; + if name.is_empty() { + throw_unsup_format!("environment variable name is an empty string"); + } else if name.to_string_lossy().contains('=') { + throw_unsup_format!("environment variable name contains '='"); + } else if this.ptr_is_null(value_ptr)? { + // Delete environment variable `{name}` + if let Some(var) = this.machine.env_vars.map.remove(&name) { + this.deallocate_ptr(var, None, MiriMemoryKind::Runtime.into())?; + this.update_environ()?; + } + Ok(1) // return non-zero on success + } else { + let value = this.read_os_str_from_wide_str(value_ptr)?; + let var_ptr = alloc_env_var_as_wide_str(&name, &value, this)?; + if let Some(var) = this.machine.env_vars.map.insert(name, var_ptr) { + this.deallocate_ptr(var, None, MiriMemoryKind::Runtime.into())?; + } + this.update_environ()?; + Ok(1) // return non-zero on success + } + } + + fn unsetenv(&mut self, name_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + this.assert_target_os_is_unix("unsetenv"); + + let name_ptr = this.read_pointer(name_op)?; + let mut success = None; + if !this.ptr_is_null(name_ptr)? { + let name = this.read_os_str_from_c_str(name_ptr)?.to_owned(); + if !name.is_empty() && !name.to_string_lossy().contains('=') { + success = Some(this.machine.env_vars.map.remove(&name)); + } + } + if let Some(old) = success { + if let Some(var) = old { + this.deallocate_ptr(var, None, MiriMemoryKind::Runtime.into())?; + } + this.update_environ()?; + Ok(0) + } else { + // name argument is a null pointer, points to an empty string, or points to a string containing an '=' character. + let einval = this.eval_libc("EINVAL")?; + this.set_last_error(einval)?; + Ok(-1) + } + } + + fn getcwd( + &mut self, + buf_op: &OpTy<'tcx, Provenance>, + size_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, Pointer>> { + let this = self.eval_context_mut(); + this.assert_target_os_is_unix("getcwd"); + + let buf = this.read_pointer(buf_op)?; + let size = this.read_scalar(size_op)?.to_machine_usize(&*this.tcx)?; + + if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { + this.reject_in_isolation("`getcwd`", reject_with)?; + this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?; + return Ok(Pointer::null()); + } + + // If we cannot get the current directory, we return null + match env::current_dir() { + Ok(cwd) => { + if this.write_path_to_c_str(&cwd, buf, size)?.0 { + return Ok(buf); + } + let erange = this.eval_libc("ERANGE")?; + this.set_last_error(erange)?; + } + Err(e) => this.set_last_error_from_io_error(e.kind())?, + } + + Ok(Pointer::null()) + } + + #[allow(non_snake_case)] + fn GetCurrentDirectoryW( + &mut self, + size_op: &OpTy<'tcx, Provenance>, // DWORD + buf_op: &OpTy<'tcx, Provenance>, // LPTSTR + ) -> InterpResult<'tcx, u32> { + let this = self.eval_context_mut(); + this.assert_target_os("windows", "GetCurrentDirectoryW"); + + let size = u64::from(this.read_scalar(size_op)?.to_u32()?); + let buf = this.read_pointer(buf_op)?; + + if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { + this.reject_in_isolation("`GetCurrentDirectoryW`", reject_with)?; + this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?; + return Ok(0); + } + + // If we cannot get the current directory, we return 0 + match env::current_dir() { + Ok(cwd) => + return Ok(windows_check_buffer_size(this.write_path_to_wide_str(&cwd, buf, size)?)), + Err(e) => this.set_last_error_from_io_error(e.kind())?, + } + Ok(0) + } + + fn chdir(&mut self, path_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + this.assert_target_os_is_unix("chdir"); + + let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?; + + if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { + this.reject_in_isolation("`chdir`", reject_with)?; + this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?; + + return Ok(-1); + } + + match env::set_current_dir(path) { + Ok(()) => Ok(0), + Err(e) => { + this.set_last_error_from_io_error(e.kind())?; + Ok(-1) + } + } + } + + #[allow(non_snake_case)] + fn SetCurrentDirectoryW( + &mut self, + path_op: &OpTy<'tcx, Provenance>, // LPCTSTR + ) -> InterpResult<'tcx, i32> { + // ^ Returns BOOL (i32 on Windows) + + let this = self.eval_context_mut(); + this.assert_target_os("windows", "SetCurrentDirectoryW"); + + let path = this.read_path_from_wide_str(this.read_pointer(path_op)?)?; + + if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { + this.reject_in_isolation("`SetCurrentDirectoryW`", reject_with)?; + this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?; + + return Ok(0); + } + + match env::set_current_dir(path) { + Ok(()) => Ok(1), + Err(e) => { + this.set_last_error_from_io_error(e.kind())?; + Ok(0) + } + } + } + + /// Updates the `environ` static. + /// The first time it gets called, also initializes `extra.environ`. + fn update_environ(&mut self) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + // Deallocate the old environ list, if any. + if let Some(environ) = this.machine.env_vars.environ { + let old_vars_ptr = this.read_pointer(&environ.into())?; + this.deallocate_ptr(old_vars_ptr, None, MiriMemoryKind::Runtime.into())?; + } else { + // No `environ` allocated yet, let's do that. + // This is memory backing an extern static, hence `ExternStatic`, not `Env`. + let layout = this.machine.layouts.mut_raw_ptr; + let place = this.allocate(layout, MiriMemoryKind::ExternStatic.into())?; + this.machine.env_vars.environ = Some(place); + } + + // Collect all the pointers to each variable in a vector. + let mut vars: Vec>> = + this.machine.env_vars.map.values().copied().collect(); + // Add the trailing null pointer. + vars.push(Pointer::null()); + // Make an array with all these pointers inside Miri. + let tcx = this.tcx; + let vars_layout = this.layout_of( + tcx.mk_array(this.machine.layouts.mut_raw_ptr.ty, u64::try_from(vars.len()).unwrap()), + )?; + let vars_place = this.allocate(vars_layout, MiriMemoryKind::Runtime.into())?; + for (idx, var) in vars.into_iter().enumerate() { + let place = this.mplace_field(&vars_place, idx)?; + this.write_pointer(var, &place.into())?; + } + this.write_pointer(vars_place.ptr, &this.machine.env_vars.environ.unwrap().into())?; + + Ok(()) + } + + fn getpid(&mut self) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + this.assert_target_os_is_unix("getpid"); + + this.check_no_isolation("`getpid`")?; + + // The reason we need to do this wacky of a conversion is because + // `libc::getpid` returns an i32, however, `std::process::id()` return an u32. + // So we un-do the conversion that stdlib does and turn it back into an i32. + #[allow(clippy::cast_possible_wrap)] + Ok(std::process::id() as i32) + } + + #[allow(non_snake_case)] + fn GetCurrentProcessId(&mut self) -> InterpResult<'tcx, u32> { + let this = self.eval_context_mut(); + this.assert_target_os("windows", "GetCurrentProcessId"); + + this.check_no_isolation("`GetCurrentProcessId`")?; + + Ok(std::process::id()) + } +} diff --git a/src/tools/miri/src/shims/ffi_support.rs b/src/tools/miri/src/shims/ffi_support.rs new file mode 100644 index 0000000000000..0813554e9d24e --- /dev/null +++ b/src/tools/miri/src/shims/ffi_support.rs @@ -0,0 +1,291 @@ +use libffi::{high::call as ffi, low::CodePtr}; +use std::ops::Deref; + +use rustc_middle::ty::{self as ty, IntTy, Ty, UintTy}; +use rustc_span::Symbol; +use rustc_target::abi::HasDataLayout; + +use crate::*; + +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} + +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + /// Extract the scalar value from the result of reading a scalar from the machine, + /// and convert it to a `CArg`. + fn scalar_to_carg( + k: Scalar, + arg_type: Ty<'tcx>, + cx: &impl HasDataLayout, + ) -> InterpResult<'tcx, CArg> { + match arg_type.kind() { + // If the primitive provided can be converted to a type matching the type pattern + // then create a `CArg` of this primitive value with the corresponding `CArg` constructor. + // the ints + ty::Int(IntTy::I8) => { + return Ok(CArg::Int8(k.to_i8()?)); + } + ty::Int(IntTy::I16) => { + return Ok(CArg::Int16(k.to_i16()?)); + } + ty::Int(IntTy::I32) => { + return Ok(CArg::Int32(k.to_i32()?)); + } + ty::Int(IntTy::I64) => { + return Ok(CArg::Int64(k.to_i64()?)); + } + ty::Int(IntTy::Isize) => { + // This will fail if host != target, but then the entire FFI thing probably won't work well + // in that situation. + return Ok(CArg::ISize(k.to_machine_isize(cx)?.try_into().unwrap())); + } + // the uints + ty::Uint(UintTy::U8) => { + return Ok(CArg::UInt8(k.to_u8()?)); + } + ty::Uint(UintTy::U16) => { + return Ok(CArg::UInt16(k.to_u16()?)); + } + ty::Uint(UintTy::U32) => { + return Ok(CArg::UInt32(k.to_u32()?)); + } + ty::Uint(UintTy::U64) => { + return Ok(CArg::UInt64(k.to_u64()?)); + } + ty::Uint(UintTy::Usize) => { + // This will fail if host != target, but then the entire FFI thing probably won't work well + // in that situation. + return Ok(CArg::USize(k.to_machine_usize(cx)?.try_into().unwrap())); + } + _ => {} + } + // If no primitives were returned then we have an unsupported type. + throw_unsup_format!( + "unsupported scalar argument type to external C function: {:?}", + arg_type + ); + } + + /// Call external C function and + /// store output, depending on return type in the function signature. + fn call_external_c_and_store_return<'a>( + &mut self, + link_name: Symbol, + dest: &PlaceTy<'tcx, Provenance>, + ptr: CodePtr, + libffi_args: Vec>, + ) -> InterpResult<'tcx, ()> { + let this = self.eval_context_mut(); + + // Unsafe because of the call to external C code. + // Because this is calling a C function it is not necessarily sound, + // but there is no way around this and we've checked as much as we can. + unsafe { + // If the return type of a function is a primitive integer type, + // then call the function (`ptr`) with arguments `libffi_args`, store the return value as the specified + // primitive integer type, and then write this value out to the miri memory as an integer. + match dest.layout.ty.kind() { + // ints + ty::Int(IntTy::I8) => { + let x = ffi::call::(ptr, libffi_args.as_slice()); + this.write_int(x, dest)?; + return Ok(()); + } + ty::Int(IntTy::I16) => { + let x = ffi::call::(ptr, libffi_args.as_slice()); + this.write_int(x, dest)?; + return Ok(()); + } + ty::Int(IntTy::I32) => { + let x = ffi::call::(ptr, libffi_args.as_slice()); + this.write_int(x, dest)?; + return Ok(()); + } + ty::Int(IntTy::I64) => { + let x = ffi::call::(ptr, libffi_args.as_slice()); + this.write_int(x, dest)?; + return Ok(()); + } + ty::Int(IntTy::Isize) => { + let x = ffi::call::(ptr, libffi_args.as_slice()); + // `isize` doesn't `impl Into`, so convert manually. + // Convert to `i64` since this covers both 32- and 64-bit machines. + this.write_int(i64::try_from(x).unwrap(), dest)?; + return Ok(()); + } + // uints + ty::Uint(UintTy::U8) => { + let x = ffi::call::(ptr, libffi_args.as_slice()); + this.write_int(x, dest)?; + return Ok(()); + } + ty::Uint(UintTy::U16) => { + let x = ffi::call::(ptr, libffi_args.as_slice()); + this.write_int(x, dest)?; + return Ok(()); + } + ty::Uint(UintTy::U32) => { + let x = ffi::call::(ptr, libffi_args.as_slice()); + this.write_int(x, dest)?; + return Ok(()); + } + ty::Uint(UintTy::U64) => { + let x = ffi::call::(ptr, libffi_args.as_slice()); + this.write_int(x, dest)?; + return Ok(()); + } + ty::Uint(UintTy::Usize) => { + let x = ffi::call::(ptr, libffi_args.as_slice()); + // `usize` doesn't `impl Into`, so convert manually. + // Convert to `u64` since this covers both 32- and 64-bit machines. + this.write_int(u64::try_from(x).unwrap(), dest)?; + return Ok(()); + } + // Functions with no declared return type (i.e., the default return) + // have the output_type `Tuple([])`. + ty::Tuple(t_list) => + if t_list.len() == 0 { + ffi::call::<()>(ptr, libffi_args.as_slice()); + return Ok(()); + }, + _ => {} + } + // FIXME ellen! deal with all the other return types + throw_unsup_format!("unsupported return type to external C function: {:?}", link_name); + } + } + + /// Get the pointer to the function of the specified name in the shared object file, + /// if it exists. The function must be in the shared object file specified: we do *not* + /// return pointers to functions in dependencies of the library. + fn get_func_ptr_explicitly_from_lib(&mut self, link_name: Symbol) -> Option { + let this = self.eval_context_mut(); + // Try getting the function from the shared library. + // On windows `_lib_path` will be unused, hence the name starting with `_`. + let (lib, _lib_path) = this.machine.external_so_lib.as_ref().unwrap(); + let func: libloading::Symbol<'_, unsafe extern "C" fn()> = unsafe { + match lib.get(link_name.as_str().as_bytes()) { + Ok(x) => x, + Err(_) => { + return None; + } + } + }; + + // FIXME: this is a hack! + // The `libloading` crate will automatically load system libraries like `libc`. + // On linux `libloading` is based on `dlsym`: https://docs.rs/libloading/0.7.3/src/libloading/os/unix/mod.rs.html#202 + // and `dlsym`(https://linux.die.net/man/3/dlsym) looks through the dependency tree of the + // library if it can't find the symbol in the library itself. + // So, in order to check if the function was actually found in the specified + // `machine.external_so_lib` we need to check its `dli_fname` and compare it to + // the specified SO file path. + // This code is a reimplementation of the mechanism for getting `dli_fname` in `libloading`, + // from: https://docs.rs/libloading/0.7.3/src/libloading/os/unix/mod.rs.html#411 + // using the `libc` crate where this interface is public. + // No `libc::dladdr` on windows. + #[cfg(unix)] + let mut info = std::mem::MaybeUninit::::uninit(); + #[cfg(unix)] + unsafe { + if libc::dladdr(*func.deref() as *const _, info.as_mut_ptr()) != 0 { + if std::ffi::CStr::from_ptr(info.assume_init().dli_fname).to_str().unwrap() + != _lib_path.to_str().unwrap() + { + return None; + } + } + } + // Return a pointer to the function. + Some(CodePtr(*func.deref() as *mut _)) + } + + /// Call specified external C function, with supplied arguments. + /// Need to convert all the arguments from their hir representations to + /// a form compatible with C (through `libffi` call). + /// Then, convert return from the C call into a corresponding form that + /// can be stored in Miri internal memory. + fn call_external_c_fct( + &mut self, + link_name: Symbol, + dest: &PlaceTy<'tcx, Provenance>, + args: &[OpTy<'tcx, Provenance>], + ) -> InterpResult<'tcx, bool> { + // Get the pointer to the function in the shared object file if it exists. + let code_ptr = match self.get_func_ptr_explicitly_from_lib(link_name) { + Some(ptr) => ptr, + None => { + // Shared object file does not export this function -- try the shims next. + return Ok(false); + } + }; + + let this = self.eval_context_mut(); + + // Get the function arguments, and convert them to `libffi`-compatible form. + let mut libffi_args = Vec::::with_capacity(args.len()); + for cur_arg in args.iter() { + libffi_args.push(Self::scalar_to_carg( + this.read_scalar(cur_arg)?, + cur_arg.layout.ty, + this, + )?); + } + + // Convert them to `libffi::high::Arg` type. + let libffi_args = libffi_args + .iter() + .map(|cur_arg| cur_arg.arg_downcast()) + .collect::>>(); + + // Call the function and store output, depending on return type in the function signature. + self.call_external_c_and_store_return(link_name, dest, code_ptr, libffi_args)?; + Ok(true) + } +} + +#[derive(Debug, Clone)] +/// Enum of supported arguments to external C functions. +// We introduce this enum instead of just calling `ffi::arg` and storing a list +// of `libffi::high::Arg` directly, because the `libffi::high::Arg` just wraps a reference +// to the value it represents: https://docs.rs/libffi/latest/libffi/high/call/struct.Arg.html +// and we need to store a copy of the value, and pass a reference to this copy to C instead. +pub enum CArg { + /// 8-bit signed integer. + Int8(i8), + /// 16-bit signed integer. + Int16(i16), + /// 32-bit signed integer. + Int32(i32), + /// 64-bit signed integer. + Int64(i64), + /// isize. + ISize(isize), + /// 8-bit unsigned integer. + UInt8(u8), + /// 16-bit unsigned integer. + UInt16(u16), + /// 32-bit unsigned integer. + UInt32(u32), + /// 64-bit unsigned integer. + UInt64(u64), + /// usize. + USize(usize), +} + +impl<'a> CArg { + /// Convert a `CArg` to a `libffi` argument type. + fn arg_downcast(&'a self) -> libffi::high::Arg<'a> { + match self { + CArg::Int8(i) => ffi::arg(i), + CArg::Int16(i) => ffi::arg(i), + CArg::Int32(i) => ffi::arg(i), + CArg::Int64(i) => ffi::arg(i), + CArg::ISize(i) => ffi::arg(i), + CArg::UInt8(i) => ffi::arg(i), + CArg::UInt16(i) => ffi::arg(i), + CArg::UInt32(i) => ffi::arg(i), + CArg::UInt64(i) => ffi::arg(i), + CArg::USize(i) => ffi::arg(i), + } + } +} diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs new file mode 100644 index 0000000000000..bb62a2a7ec1b5 --- /dev/null +++ b/src/tools/miri/src/shims/foreign_items.rs @@ -0,0 +1,811 @@ +use std::{collections::hash_map::Entry, iter}; + +use log::trace; + +use rustc_apfloat::Float; +use rustc_ast::expand::allocator::AllocatorKind; +use rustc_hir::{ + def::DefKind, + def_id::{CrateNum, DefId, LOCAL_CRATE}, +}; +use rustc_middle::middle::{ + codegen_fn_attrs::CodegenFnAttrFlags, dependency_format::Linkage, + exported_symbols::ExportedSymbol, +}; +use rustc_middle::mir; +use rustc_middle::ty; +use rustc_session::config::CrateType; +use rustc_span::Symbol; +use rustc_target::{ + abi::{Align, Size}, + spec::abi::Abi, +}; + +use super::backtrace::EvalContextExt as _; +use crate::helpers::{convert::Truncate, target_os_is_unix}; +#[cfg(unix)] +use crate::shims::ffi_support::EvalContextExt as _; +use crate::*; + +/// Returned by `emulate_foreign_item_by_name`. +pub enum EmulateByNameResult<'mir, 'tcx> { + /// The caller is expected to jump to the return block. + NeedsJumping, + /// Jumping has already been taken care of. + AlreadyJumped, + /// A MIR body has been found for the function. + MirBody(&'mir mir::Body<'tcx>, ty::Instance<'tcx>), + /// The item is not supported. + NotSupported, +} + +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + /// Returns the minimum alignment for the target architecture for allocations of the given size. + fn min_align(&self, size: u64, kind: MiriMemoryKind) -> Align { + let this = self.eval_context_ref(); + // List taken from `library/std/src/sys/common/alloc.rs`. + // This list should be kept in sync with the one from libstd. + let min_align = match this.tcx.sess.target.arch.as_ref() { + "x86" | "arm" | "mips" | "powerpc" | "powerpc64" | "asmjs" | "wasm32" => 8, + "x86_64" | "aarch64" | "mips64" | "s390x" | "sparc64" => 16, + arch => bug!("Unsupported target architecture: {}", arch), + }; + // Windows always aligns, even small allocations. + // Source: + // But jemalloc does not, so for the C heap we only align if the allocation is sufficiently big. + if kind == MiriMemoryKind::WinHeap || size >= min_align { + return Align::from_bytes(min_align).unwrap(); + } + // We have `size < min_align`. Round `size` *down* to the next power of two and use that. + fn prev_power_of_two(x: u64) -> u64 { + let next_pow2 = x.next_power_of_two(); + if next_pow2 == x { + // x *is* a power of two, just use that. + x + } else { + // x is between two powers, so next = 2*prev. + next_pow2 / 2 + } + } + Align::from_bytes(prev_power_of_two(size)).unwrap() + } + + fn malloc( + &mut self, + size: u64, + zero_init: bool, + kind: MiriMemoryKind, + ) -> InterpResult<'tcx, Pointer>> { + let this = self.eval_context_mut(); + if size == 0 { + Ok(Pointer::null()) + } else { + let align = this.min_align(size, kind); + let ptr = this.allocate_ptr(Size::from_bytes(size), align, kind.into())?; + if zero_init { + // We just allocated this, the access is definitely in-bounds and fits into our address space. + this.write_bytes_ptr( + ptr.into(), + iter::repeat(0u8).take(usize::try_from(size).unwrap()), + ) + .unwrap(); + } + Ok(ptr.into()) + } + } + + fn free( + &mut self, + ptr: Pointer>, + kind: MiriMemoryKind, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + if !this.ptr_is_null(ptr)? { + this.deallocate_ptr(ptr, None, kind.into())?; + } + Ok(()) + } + + fn realloc( + &mut self, + old_ptr: Pointer>, + new_size: u64, + kind: MiriMemoryKind, + ) -> InterpResult<'tcx, Pointer>> { + let this = self.eval_context_mut(); + let new_align = this.min_align(new_size, kind); + if this.ptr_is_null(old_ptr)? { + if new_size == 0 { + Ok(Pointer::null()) + } else { + let new_ptr = + this.allocate_ptr(Size::from_bytes(new_size), new_align, kind.into())?; + Ok(new_ptr.into()) + } + } else { + if new_size == 0 { + this.deallocate_ptr(old_ptr, None, kind.into())?; + Ok(Pointer::null()) + } else { + let new_ptr = this.reallocate_ptr( + old_ptr, + None, + Size::from_bytes(new_size), + new_align, + kind.into(), + )?; + Ok(new_ptr.into()) + } + } + } + + /// Lookup the body of a function that has `link_name` as the symbol name. + fn lookup_exported_symbol( + &mut self, + link_name: Symbol, + ) -> InterpResult<'tcx, Option<(&'mir mir::Body<'tcx>, ty::Instance<'tcx>)>> { + let this = self.eval_context_mut(); + let tcx = this.tcx.tcx; + + // If the result was cached, just return it. + // (Cannot use `or_insert` since the code below might have to throw an error.) + let entry = this.machine.exported_symbols_cache.entry(link_name); + let instance = *match entry { + Entry::Occupied(e) => e.into_mut(), + Entry::Vacant(e) => { + // Find it if it was not cached. + let mut instance_and_crate: Option<(ty::Instance<'_>, CrateNum)> = None; + // `dependency_formats` includes all the transitive informations needed to link a crate, + // which is what we need here since we need to dig out `exported_symbols` from all transitive + // dependencies. + let dependency_formats = tcx.dependency_formats(()); + let dependency_format = dependency_formats + .iter() + .find(|(crate_type, _)| *crate_type == CrateType::Executable) + .expect("interpreting a non-executable crate"); + for cnum in iter::once(LOCAL_CRATE).chain( + dependency_format.1.iter().enumerate().filter_map(|(num, &linkage)| { + // We add 1 to the number because that's what rustc also does everywhere it + // calls `CrateNum::new`... + #[allow(clippy::integer_arithmetic)] + (linkage != Linkage::NotLinked).then_some(CrateNum::new(num + 1)) + }), + ) { + // We can ignore `_export_info` here: we are a Rust crate, and everything is exported + // from a Rust crate. + for &(symbol, _export_info) in tcx.exported_symbols(cnum) { + if let ExportedSymbol::NonGeneric(def_id) = symbol { + let attrs = tcx.codegen_fn_attrs(def_id); + let symbol_name = if let Some(export_name) = attrs.export_name { + export_name + } else if attrs.flags.contains(CodegenFnAttrFlags::NO_MANGLE) { + tcx.item_name(def_id) + } else { + // Skip over items without an explicitly defined symbol name. + continue; + }; + if symbol_name == link_name { + if let Some((original_instance, original_cnum)) = instance_and_crate + { + // Make sure we are consistent wrt what is 'first' and 'second'. + let original_span = + tcx.def_span(original_instance.def_id()).data(); + let span = tcx.def_span(def_id).data(); + if original_span < span { + throw_machine_stop!( + TerminationInfo::MultipleSymbolDefinitions { + link_name, + first: original_span, + first_crate: tcx.crate_name(original_cnum), + second: span, + second_crate: tcx.crate_name(cnum), + } + ); + } else { + throw_machine_stop!( + TerminationInfo::MultipleSymbolDefinitions { + link_name, + first: span, + first_crate: tcx.crate_name(cnum), + second: original_span, + second_crate: tcx.crate_name(original_cnum), + } + ); + } + } + if !matches!(tcx.def_kind(def_id), DefKind::Fn | DefKind::AssocFn) { + throw_ub_format!( + "attempt to call an exported symbol that is not defined as a function" + ); + } + instance_and_crate = Some((ty::Instance::mono(tcx, def_id), cnum)); + } + } + } + } + + e.insert(instance_and_crate.map(|ic| ic.0)) + } + }; + match instance { + None => Ok(None), // no symbol with this name + Some(instance) => Ok(Some((this.load_mir(instance.def, None)?, instance))), + } + } + + /// Emulates calling a foreign item, failing if the item is not supported. + /// This function will handle `goto_block` if needed. + /// Returns Ok(None) if the foreign item was completely handled + /// by this function. + /// Returns Ok(Some(body)) if processing the foreign item + /// is delegated to another function. + fn emulate_foreign_item( + &mut self, + def_id: DefId, + abi: Abi, + args: &[OpTy<'tcx, Provenance>], + dest: &PlaceTy<'tcx, Provenance>, + ret: Option, + unwind: StackPopUnwind, + ) -> InterpResult<'tcx, Option<(&'mir mir::Body<'tcx>, ty::Instance<'tcx>)>> { + let this = self.eval_context_mut(); + let link_name = this.item_link_name(def_id); + let tcx = this.tcx.tcx; + + // First: functions that diverge. + let ret = match ret { + None => + match link_name.as_str() { + "miri_start_panic" => { + // `check_shim` happens inside `handle_miri_start_panic`. + this.handle_miri_start_panic(abi, link_name, args, unwind)?; + return Ok(None); + } + // This matches calls to the foreign item `panic_impl`. + // The implementation is provided by the function with the `#[panic_handler]` attribute. + "panic_impl" => { + // We don't use `check_shim` here because we are just forwarding to the lang + // item. Argument count checking will be performed when the returned `Body` is + // called. + this.check_abi_and_shim_symbol_clash(abi, Abi::Rust, link_name)?; + let panic_impl_id = tcx.lang_items().panic_impl().unwrap(); + let panic_impl_instance = ty::Instance::mono(tcx, panic_impl_id); + return Ok(Some(( + this.load_mir(panic_impl_instance.def, None)?, + panic_impl_instance, + ))); + } + #[rustfmt::skip] + | "exit" + | "ExitProcess" + => { + let exp_abi = if link_name.as_str() == "exit" { + Abi::C { unwind: false } + } else { + Abi::System { unwind: false } + }; + let [code] = this.check_shim(abi, exp_abi, link_name, args)?; + // it's really u32 for ExitProcess, but we have to put it into the `Exit` variant anyway + let code = this.read_scalar(code)?.to_i32()?; + throw_machine_stop!(TerminationInfo::Exit(code.into())); + } + "abort" => { + let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + throw_machine_stop!(TerminationInfo::Abort( + "the program aborted execution".to_owned() + )) + } + _ => { + if let Some(body) = this.lookup_exported_symbol(link_name)? { + return Ok(Some(body)); + } + this.handle_unsupported(format!( + "can't call (diverging) foreign function: {}", + link_name + ))?; + return Ok(None); + } + }, + Some(p) => p, + }; + + // Second: functions that return immediately. + match this.emulate_foreign_item_by_name(link_name, abi, args, dest)? { + EmulateByNameResult::NeedsJumping => { + trace!("{:?}", this.dump_place(**dest)); + this.go_to_block(ret); + } + EmulateByNameResult::AlreadyJumped => (), + EmulateByNameResult::MirBody(mir, instance) => return Ok(Some((mir, instance))), + EmulateByNameResult::NotSupported => { + if let Some(body) = this.lookup_exported_symbol(link_name)? { + return Ok(Some(body)); + } + + this.handle_unsupported(format!("can't call foreign function: {}", link_name))?; + return Ok(None); + } + } + + Ok(None) + } + + /// Emulates calling the internal __rust_* allocator functions + fn emulate_allocator( + &mut self, + symbol: Symbol, + default: impl FnOnce(&mut MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx>, + ) -> InterpResult<'tcx, EmulateByNameResult<'mir, 'tcx>> { + let this = self.eval_context_mut(); + + let allocator_kind = if let Some(allocator_kind) = this.tcx.allocator_kind(()) { + allocator_kind + } else { + // in real code, this symbol does not exist without an allocator + return Ok(EmulateByNameResult::NotSupported); + }; + + match allocator_kind { + AllocatorKind::Global => { + let (body, instance) = this + .lookup_exported_symbol(symbol)? + .expect("symbol should be present if there is a global allocator"); + + Ok(EmulateByNameResult::MirBody(body, instance)) + } + AllocatorKind::Default => { + default(this)?; + Ok(EmulateByNameResult::NeedsJumping) + } + } + } + + /// Emulates calling a foreign item using its name. + fn emulate_foreign_item_by_name( + &mut self, + link_name: Symbol, + abi: Abi, + args: &[OpTy<'tcx, Provenance>], + dest: &PlaceTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, EmulateByNameResult<'mir, 'tcx>> { + let this = self.eval_context_mut(); + + // First deal with any external C functions in linked .so file. + #[cfg(unix)] + if this.machine.external_so_lib.as_ref().is_some() { + // An Ok(false) here means that the function being called was not exported + // by the specified `.so` file; we should continue and check if it corresponds to + // a provided shim. + if this.call_external_c_fct(link_name, dest, args)? { + return Ok(EmulateByNameResult::NeedsJumping); + } + } + + // When adding a new shim, you should follow the following pattern: + // ``` + // "shim_name" => { + // let [arg1, arg2, arg3] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + // let result = this.shim_name(arg1, arg2, arg3)?; + // this.write_scalar(result, dest)?; + // } + // ``` + // and then define `shim_name` as a helper function in an extension trait in a suitable file + // (see e.g. `unix/fs.rs`): + // ``` + // fn shim_name( + // &mut self, + // arg1: &OpTy<'tcx, Provenance>, + // arg2: &OpTy<'tcx, Provenance>, + // arg3: &OpTy<'tcx, Provenance>) + // -> InterpResult<'tcx, Scalar> { + // let this = self.eval_context_mut(); + // + // // First thing: load all the arguments. Details depend on the shim. + // let arg1 = this.read_scalar(arg1)?.to_u32()?; + // let arg2 = this.read_pointer(arg2)?; // when you need to work with the pointer directly + // let arg3 = this.deref_operand(arg3)?; // when you want to load/store through the pointer at its declared type + // + // // ... + // + // Ok(Scalar::from_u32(42)) + // } + // ``` + // You might find existing shims not following this pattern, most + // likely because they predate it or because for some reason they cannot be made to fit. + + // Here we dispatch all the shims for foreign functions. If you have a platform specific + // shim, add it to the corresponding submodule. + match link_name.as_str() { + // Miri-specific extern functions + "miri_static_root" => { + let [ptr] = this.check_shim(abi, Abi::Rust, link_name, args)?; + let ptr = this.read_pointer(ptr)?; + let (alloc_id, offset, _) = this.ptr_get_alloc_id(ptr)?; + if offset != Size::ZERO { + throw_unsup_format!("pointer passed to miri_static_root must point to beginning of an allocated block"); + } + this.machine.static_roots.push(alloc_id); + } + + // Obtains the size of a Miri backtrace. See the README for details. + "miri_backtrace_size" => { + this.handle_miri_backtrace_size(abi, link_name, args, dest)?; + } + + // Obtains a Miri backtrace. See the README for details. + "miri_get_backtrace" => { + // `check_shim` happens inside `handle_miri_get_backtrace`. + this.handle_miri_get_backtrace(abi, link_name, args, dest)?; + } + + // Resolves a Miri backtrace frame. See the README for details. + "miri_resolve_frame" => { + // `check_shim` happens inside `handle_miri_resolve_frame`. + this.handle_miri_resolve_frame(abi, link_name, args, dest)?; + } + + // Writes the function and file names of a Miri backtrace frame into a user provided buffer. See the README for details. + "miri_resolve_frame_names" => { + this.handle_miri_resolve_frame_names(abi, link_name, args)?; + } + + // Standard C allocation + "malloc" => { + let [size] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let size = this.read_scalar(size)?.to_machine_usize(this)?; + let res = this.malloc(size, /*zero_init:*/ false, MiriMemoryKind::C)?; + this.write_pointer(res, dest)?; + } + "calloc" => { + let [items, len] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let items = this.read_scalar(items)?.to_machine_usize(this)?; + let len = this.read_scalar(len)?.to_machine_usize(this)?; + let size = + items.checked_mul(len).ok_or_else(|| err_ub_format!("overflow during calloc size computation"))?; + let res = this.malloc(size, /*zero_init:*/ true, MiriMemoryKind::C)?; + this.write_pointer(res, dest)?; + } + "free" => { + let [ptr] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let ptr = this.read_pointer(ptr)?; + this.free(ptr, MiriMemoryKind::C)?; + } + "realloc" => { + let [old_ptr, new_size] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let old_ptr = this.read_pointer(old_ptr)?; + let new_size = this.read_scalar(new_size)?.to_machine_usize(this)?; + let res = this.realloc(old_ptr, new_size, MiriMemoryKind::C)?; + this.write_pointer(res, dest)?; + } + + // Rust allocation + "__rust_alloc" => { + let [size, align] = this.check_shim(abi, Abi::Rust, link_name, args)?; + let size = this.read_scalar(size)?.to_machine_usize(this)?; + let align = this.read_scalar(align)?.to_machine_usize(this)?; + + return this.emulate_allocator(Symbol::intern("__rg_alloc"), |this| { + Self::check_alloc_request(size, align)?; + + let ptr = this.allocate_ptr( + Size::from_bytes(size), + Align::from_bytes(align).unwrap(), + MiriMemoryKind::Rust.into(), + )?; + + this.write_pointer(ptr, dest) + }); + } + "__rust_alloc_zeroed" => { + let [size, align] = this.check_shim(abi, Abi::Rust, link_name, args)?; + let size = this.read_scalar(size)?.to_machine_usize(this)?; + let align = this.read_scalar(align)?.to_machine_usize(this)?; + + return this.emulate_allocator(Symbol::intern("__rg_alloc_zeroed"), |this| { + Self::check_alloc_request(size, align)?; + + let ptr = this.allocate_ptr( + Size::from_bytes(size), + Align::from_bytes(align).unwrap(), + MiriMemoryKind::Rust.into(), + )?; + + // We just allocated this, the access is definitely in-bounds. + this.write_bytes_ptr(ptr.into(), iter::repeat(0u8).take(usize::try_from(size).unwrap())).unwrap(); + this.write_pointer(ptr, dest) + }); + } + "__rust_dealloc" => { + let [ptr, old_size, align] = this.check_shim(abi, Abi::Rust, link_name, args)?; + let ptr = this.read_pointer(ptr)?; + let old_size = this.read_scalar(old_size)?.to_machine_usize(this)?; + let align = this.read_scalar(align)?.to_machine_usize(this)?; + + return this.emulate_allocator(Symbol::intern("__rg_dealloc"), |this| { + // No need to check old_size/align; we anyway check that they match the allocation. + this.deallocate_ptr( + ptr, + Some((Size::from_bytes(old_size), Align::from_bytes(align).unwrap())), + MiriMemoryKind::Rust.into(), + ) + }); + } + "__rust_realloc" => { + let [ptr, old_size, align, new_size] = this.check_shim(abi, Abi::Rust, link_name, args)?; + let ptr = this.read_pointer(ptr)?; + let old_size = this.read_scalar(old_size)?.to_machine_usize(this)?; + let align = this.read_scalar(align)?.to_machine_usize(this)?; + let new_size = this.read_scalar(new_size)?.to_machine_usize(this)?; + // No need to check old_size; we anyway check that they match the allocation. + + return this.emulate_allocator(Symbol::intern("__rg_realloc"), |this| { + Self::check_alloc_request(new_size, align)?; + + let align = Align::from_bytes(align).unwrap(); + let new_ptr = this.reallocate_ptr( + ptr, + Some((Size::from_bytes(old_size), align)), + Size::from_bytes(new_size), + align, + MiriMemoryKind::Rust.into(), + )?; + this.write_pointer(new_ptr, dest) + }); + } + + // C memory handling functions + "memcmp" => { + let [left, right, n] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let left = this.read_pointer(left)?; + let right = this.read_pointer(right)?; + let n = Size::from_bytes(this.read_scalar(n)?.to_machine_usize(this)?); + + let result = { + let left_bytes = this.read_bytes_ptr_strip_provenance(left, n)?; + let right_bytes = this.read_bytes_ptr_strip_provenance(right, n)?; + + use std::cmp::Ordering::*; + match left_bytes.cmp(right_bytes) { + Less => -1i32, + Equal => 0, + Greater => 1, + } + }; + + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "memrchr" => { + let [ptr, val, num] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let ptr = this.read_pointer(ptr)?; + let val = this.read_scalar(val)?.to_i32()?; + let num = this.read_scalar(num)?.to_machine_usize(this)?; + // The docs say val is "interpreted as unsigned char". + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] + let val = val as u8; + + if let Some(idx) = this + .read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(num))? + .iter() + .rev() + .position(|&c| c == val) + { + let idx = u64::try_from(idx).unwrap(); + #[allow(clippy::integer_arithmetic)] // idx < num, so this never wraps + let new_ptr = ptr.offset(Size::from_bytes(num - idx - 1), this)?; + this.write_pointer(new_ptr, dest)?; + } else { + this.write_null(dest)?; + } + } + "memchr" => { + let [ptr, val, num] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let ptr = this.read_pointer(ptr)?; + let val = this.read_scalar(val)?.to_i32()?; + let num = this.read_scalar(num)?.to_machine_usize(this)?; + // The docs say val is "interpreted as unsigned char". + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] + let val = val as u8; + + let idx = this + .read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(num))? + .iter() + .position(|&c| c == val); + if let Some(idx) = idx { + let new_ptr = ptr.offset(Size::from_bytes(idx as u64), this)?; + this.write_pointer(new_ptr, dest)?; + } else { + this.write_null(dest)?; + } + } + "strlen" => { + let [ptr] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let ptr = this.read_pointer(ptr)?; + let n = this.read_c_str(ptr)?.len(); + this.write_scalar(Scalar::from_machine_usize(u64::try_from(n).unwrap(), this), dest)?; + } + + // math functions (note that there are also intrinsics for some other functions) + #[rustfmt::skip] + | "cbrtf" + | "coshf" + | "sinhf" + | "tanf" + | "tanhf" + | "acosf" + | "asinf" + | "atanf" + | "log1pf" + | "expm1f" + => { + let [f] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + // FIXME: Using host floats. + let f = f32::from_bits(this.read_scalar(f)?.to_u32()?); + let res = match link_name.as_str() { + "cbrtf" => f.cbrt(), + "coshf" => f.cosh(), + "sinhf" => f.sinh(), + "tanf" => f.tan(), + "tanhf" => f.tanh(), + "acosf" => f.acos(), + "asinf" => f.asin(), + "atanf" => f.atan(), + "log1pf" => f.ln_1p(), + "expm1f" => f.exp_m1(), + _ => bug!(), + }; + this.write_scalar(Scalar::from_u32(res.to_bits()), dest)?; + } + #[rustfmt::skip] + | "_hypotf" + | "hypotf" + | "atan2f" + | "fdimf" + => { + let [f1, f2] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + // underscore case for windows, here and below + // (see https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/floating-point-primitives?view=vs-2019) + // FIXME: Using host floats. + let f1 = f32::from_bits(this.read_scalar(f1)?.to_u32()?); + let f2 = f32::from_bits(this.read_scalar(f2)?.to_u32()?); + let res = match link_name.as_str() { + "_hypotf" | "hypotf" => f1.hypot(f2), + "atan2f" => f1.atan2(f2), + #[allow(deprecated)] + "fdimf" => f1.abs_sub(f2), + _ => bug!(), + }; + this.write_scalar(Scalar::from_u32(res.to_bits()), dest)?; + } + #[rustfmt::skip] + | "cbrt" + | "cosh" + | "sinh" + | "tan" + | "tanh" + | "acos" + | "asin" + | "atan" + | "log1p" + | "expm1" + => { + let [f] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + // FIXME: Using host floats. + let f = f64::from_bits(this.read_scalar(f)?.to_u64()?); + let res = match link_name.as_str() { + "cbrt" => f.cbrt(), + "cosh" => f.cosh(), + "sinh" => f.sinh(), + "tan" => f.tan(), + "tanh" => f.tanh(), + "acos" => f.acos(), + "asin" => f.asin(), + "atan" => f.atan(), + "log1p" => f.ln_1p(), + "expm1" => f.exp_m1(), + _ => bug!(), + }; + this.write_scalar(Scalar::from_u64(res.to_bits()), dest)?; + } + #[rustfmt::skip] + | "_hypot" + | "hypot" + | "atan2" + | "fdim" + => { + let [f1, f2] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + // FIXME: Using host floats. + let f1 = f64::from_bits(this.read_scalar(f1)?.to_u64()?); + let f2 = f64::from_bits(this.read_scalar(f2)?.to_u64()?); + let res = match link_name.as_str() { + "_hypot" | "hypot" => f1.hypot(f2), + "atan2" => f1.atan2(f2), + #[allow(deprecated)] + "fdim" => f1.abs_sub(f2), + _ => bug!(), + }; + this.write_scalar(Scalar::from_u64(res.to_bits()), dest)?; + } + #[rustfmt::skip] + | "_ldexp" + | "ldexp" + | "scalbn" + => { + let [x, exp] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + // For radix-2 (binary) systems, `ldexp` and `scalbn` are the same. + let x = this.read_scalar(x)?.to_f64()?; + let exp = this.read_scalar(exp)?.to_i32()?; + + // Saturating cast to i16. Even those are outside the valid exponent range so + // `scalbn` below will do its over/underflow handling. + let exp = if exp > i32::from(i16::MAX) { + i16::MAX + } else if exp < i32::from(i16::MIN) { + i16::MIN + } else { + exp.try_into().unwrap() + }; + + let res = x.scalbn(exp); + this.write_scalar(Scalar::from_f64(res), dest)?; + } + + // Architecture-specific shims + "llvm.x86.addcarry.64" if this.tcx.sess.target.arch == "x86_64" => { + // Computes u8+u64+u64, returning tuple (u8,u64) comprising the output carry and truncated sum. + let [c_in, a, b] = this.check_shim(abi, Abi::Unadjusted, link_name, args)?; + let c_in = this.read_scalar(c_in)?.to_u8()?; + let a = this.read_scalar(a)?.to_u64()?; + let b = this.read_scalar(b)?.to_u64()?; + + #[allow(clippy::integer_arithmetic)] // adding two u64 and a u8 cannot wrap in a u128 + let wide_sum = u128::from(c_in) + u128::from(a) + u128::from(b); + #[allow(clippy::integer_arithmetic)] // it's a u128, we can shift by 64 + let (c_out, sum) = ((wide_sum >> 64).truncate::(), wide_sum.truncate::()); + + let c_out_field = this.place_field(dest, 0)?; + this.write_scalar(Scalar::from_u8(c_out), &c_out_field)?; + let sum_field = this.place_field(dest, 1)?; + this.write_scalar(Scalar::from_u64(sum), &sum_field)?; + } + "llvm.x86.sse2.pause" if this.tcx.sess.target.arch == "x86" || this.tcx.sess.target.arch == "x86_64" => { + let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + this.yield_active_thread(); + } + "llvm.aarch64.isb" if this.tcx.sess.target.arch == "aarch64" => { + let [arg] = this.check_shim(abi, Abi::Unadjusted, link_name, args)?; + let arg = this.read_scalar(arg)?.to_i32()?; + match arg { + 15 => { // SY ("full system scope") + this.yield_active_thread(); + } + _ => { + throw_unsup_format!("unsupported llvm.aarch64.isb argument {}", arg); + } + } + } + + // Platform-specific shims + _ => match this.tcx.sess.target.os.as_ref() { + target if target_os_is_unix(target) => return shims::unix::foreign_items::EvalContextExt::emulate_foreign_item_by_name(this, link_name, abi, args, dest), + "windows" => return shims::windows::foreign_items::EvalContextExt::emulate_foreign_item_by_name(this, link_name, abi, args, dest), + target => throw_unsup_format!("the target `{}` is not supported", target), + } + }; + // We only fall through to here if we did *not* hit the `_` arm above, + // i.e., if we actually emulated the function with one of the shims. + Ok(EmulateByNameResult::NeedsJumping) + } + + /// Check some basic requirements for this allocation request: + /// non-zero size, power-of-two alignment. + fn check_alloc_request(size: u64, align: u64) -> InterpResult<'tcx> { + if size == 0 { + throw_ub_format!("creating allocation with size 0"); + } + if !align.is_power_of_two() { + throw_ub_format!("creating allocation with non-power-of-two alignment {}", align); + } + Ok(()) + } +} diff --git a/src/tools/miri/src/shims/intrinsics/atomic.rs b/src/tools/miri/src/shims/intrinsics/atomic.rs new file mode 100644 index 0000000000000..50f69bdca3631 --- /dev/null +++ b/src/tools/miri/src/shims/intrinsics/atomic.rs @@ -0,0 +1,288 @@ +use rustc_middle::{mir, mir::BinOp, ty}; + +use crate::*; +use helpers::check_arg_count; + +pub enum AtomicOp { + /// The `bool` indicates whether the result of the operation should be negated + /// (must be a boolean-typed operation). + MirOp(mir::BinOp, bool), + Max, + Min, +} + +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + /// Calls the atomic intrinsic `intrinsic`; the `atomic_` prefix has already been removed. + fn emulate_atomic_intrinsic( + &mut self, + intrinsic_name: &str, + args: &[OpTy<'tcx, Provenance>], + dest: &PlaceTy<'tcx, Provenance>, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + + let intrinsic_structure: Vec<_> = intrinsic_name.split('_').collect(); + + fn read_ord<'tcx>(ord: &str) -> InterpResult<'tcx, AtomicReadOrd> { + Ok(match ord { + "seqcst" => AtomicReadOrd::SeqCst, + "acquire" => AtomicReadOrd::Acquire, + "relaxed" => AtomicReadOrd::Relaxed, + _ => throw_unsup_format!("unsupported read ordering `{ord}`"), + }) + } + + fn write_ord<'tcx>(ord: &str) -> InterpResult<'tcx, AtomicWriteOrd> { + Ok(match ord { + "seqcst" => AtomicWriteOrd::SeqCst, + "release" => AtomicWriteOrd::Release, + "relaxed" => AtomicWriteOrd::Relaxed, + _ => throw_unsup_format!("unsupported write ordering `{ord}`"), + }) + } + + fn rw_ord<'tcx>(ord: &str) -> InterpResult<'tcx, AtomicRwOrd> { + Ok(match ord { + "seqcst" => AtomicRwOrd::SeqCst, + "acqrel" => AtomicRwOrd::AcqRel, + "acquire" => AtomicRwOrd::Acquire, + "release" => AtomicRwOrd::Release, + "relaxed" => AtomicRwOrd::Relaxed, + _ => throw_unsup_format!("unsupported read-write ordering `{ord}`"), + }) + } + + fn fence_ord<'tcx>(ord: &str) -> InterpResult<'tcx, AtomicFenceOrd> { + Ok(match ord { + "seqcst" => AtomicFenceOrd::SeqCst, + "acqrel" => AtomicFenceOrd::AcqRel, + "acquire" => AtomicFenceOrd::Acquire, + "release" => AtomicFenceOrd::Release, + _ => throw_unsup_format!("unsupported fence ordering `{ord}`"), + }) + } + + match &*intrinsic_structure { + ["load", ord] => this.atomic_load(args, dest, read_ord(ord)?)?, + ["store", ord] => this.atomic_store(args, write_ord(ord)?)?, + + ["fence", ord] => this.atomic_fence_intrinsic(args, fence_ord(ord)?)?, + ["singlethreadfence", ord] => this.compiler_fence_intrinsic(args, fence_ord(ord)?)?, + + ["xchg", ord] => this.atomic_exchange(args, dest, rw_ord(ord)?)?, + ["cxchg", ord1, ord2] => + this.atomic_compare_exchange(args, dest, rw_ord(ord1)?, read_ord(ord2)?)?, + ["cxchgweak", ord1, ord2] => + this.atomic_compare_exchange_weak(args, dest, rw_ord(ord1)?, read_ord(ord2)?)?, + + ["or", ord] => + this.atomic_op(args, dest, AtomicOp::MirOp(BinOp::BitOr, false), rw_ord(ord)?)?, + ["xor", ord] => + this.atomic_op(args, dest, AtomicOp::MirOp(BinOp::BitXor, false), rw_ord(ord)?)?, + ["and", ord] => + this.atomic_op(args, dest, AtomicOp::MirOp(BinOp::BitAnd, false), rw_ord(ord)?)?, + ["nand", ord] => + this.atomic_op(args, dest, AtomicOp::MirOp(BinOp::BitAnd, true), rw_ord(ord)?)?, + ["xadd", ord] => + this.atomic_op(args, dest, AtomicOp::MirOp(BinOp::Add, false), rw_ord(ord)?)?, + ["xsub", ord] => + this.atomic_op(args, dest, AtomicOp::MirOp(BinOp::Sub, false), rw_ord(ord)?)?, + ["min", ord] => { + // Later we will use the type to indicate signed vs unsigned, + // so make sure it matches the intrinsic name. + assert!(matches!(args[1].layout.ty.kind(), ty::Int(_))); + this.atomic_op(args, dest, AtomicOp::Min, rw_ord(ord)?)?; + } + ["umin", ord] => { + // Later we will use the type to indicate signed vs unsigned, + // so make sure it matches the intrinsic name. + assert!(matches!(args[1].layout.ty.kind(), ty::Uint(_))); + this.atomic_op(args, dest, AtomicOp::Min, rw_ord(ord)?)?; + } + ["max", ord] => { + // Later we will use the type to indicate signed vs unsigned, + // so make sure it matches the intrinsic name. + assert!(matches!(args[1].layout.ty.kind(), ty::Int(_))); + this.atomic_op(args, dest, AtomicOp::Max, rw_ord(ord)?)?; + } + ["umax", ord] => { + // Later we will use the type to indicate signed vs unsigned, + // so make sure it matches the intrinsic name. + assert!(matches!(args[1].layout.ty.kind(), ty::Uint(_))); + this.atomic_op(args, dest, AtomicOp::Max, rw_ord(ord)?)?; + } + + _ => throw_unsup_format!("unimplemented intrinsic: `atomic_{intrinsic_name}`"), + } + Ok(()) + } +} + +impl<'mir, 'tcx: 'mir> EvalContextPrivExt<'mir, 'tcx> for MiriInterpCx<'mir, 'tcx> {} +trait EvalContextPrivExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> { + fn atomic_load( + &mut self, + args: &[OpTy<'tcx, Provenance>], + dest: &PlaceTy<'tcx, Provenance>, + atomic: AtomicReadOrd, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + + let [place] = check_arg_count(args)?; + let place = this.deref_operand(place)?; + + // Perform atomic load. + let val = this.read_scalar_atomic(&place, atomic)?; + // Perform regular store. + this.write_scalar(val, dest)?; + Ok(()) + } + + fn atomic_store( + &mut self, + args: &[OpTy<'tcx, Provenance>], + atomic: AtomicWriteOrd, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + + let [place, val] = check_arg_count(args)?; + let place = this.deref_operand(place)?; + + // Perform regular load. + let val = this.read_scalar(val)?; + // Perform atomic store + this.write_scalar_atomic(val, &place, atomic)?; + Ok(()) + } + + fn compiler_fence_intrinsic( + &mut self, + args: &[OpTy<'tcx, Provenance>], + atomic: AtomicFenceOrd, + ) -> InterpResult<'tcx> { + let [] = check_arg_count(args)?; + let _ = atomic; + //FIXME: compiler fences are currently ignored + Ok(()) + } + + fn atomic_fence_intrinsic( + &mut self, + args: &[OpTy<'tcx, Provenance>], + atomic: AtomicFenceOrd, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + let [] = check_arg_count(args)?; + this.atomic_fence(atomic)?; + Ok(()) + } + + fn atomic_op( + &mut self, + args: &[OpTy<'tcx, Provenance>], + dest: &PlaceTy<'tcx, Provenance>, + atomic_op: AtomicOp, + atomic: AtomicRwOrd, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + + let [place, rhs] = check_arg_count(args)?; + let place = this.deref_operand(place)?; + let rhs = this.read_immediate(rhs)?; + + if !place.layout.ty.is_integral() && !place.layout.ty.is_unsafe_ptr() { + span_bug!( + this.cur_span(), + "atomic arithmetic operations only work on integer and raw pointer types", + ); + } + if rhs.layout.ty != place.layout.ty { + span_bug!(this.cur_span(), "atomic arithmetic operation type mismatch"); + } + + match atomic_op { + AtomicOp::Min => { + let old = this.atomic_min_max_scalar(&place, rhs, true, atomic)?; + this.write_immediate(*old, dest)?; // old value is returned + Ok(()) + } + AtomicOp::Max => { + let old = this.atomic_min_max_scalar(&place, rhs, false, atomic)?; + this.write_immediate(*old, dest)?; // old value is returned + Ok(()) + } + AtomicOp::MirOp(op, neg) => { + let old = this.atomic_op_immediate(&place, &rhs, op, neg, atomic)?; + this.write_immediate(*old, dest)?; // old value is returned + Ok(()) + } + } + } + + fn atomic_exchange( + &mut self, + args: &[OpTy<'tcx, Provenance>], + dest: &PlaceTy<'tcx, Provenance>, + atomic: AtomicRwOrd, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + + let [place, new] = check_arg_count(args)?; + let place = this.deref_operand(place)?; + let new = this.read_scalar(new)?; + + let old = this.atomic_exchange_scalar(&place, new, atomic)?; + this.write_scalar(old, dest)?; // old value is returned + Ok(()) + } + + fn atomic_compare_exchange_impl( + &mut self, + args: &[OpTy<'tcx, Provenance>], + dest: &PlaceTy<'tcx, Provenance>, + success: AtomicRwOrd, + fail: AtomicReadOrd, + can_fail_spuriously: bool, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + + let [place, expect_old, new] = check_arg_count(args)?; + let place = this.deref_operand(place)?; + let expect_old = this.read_immediate(expect_old)?; // read as immediate for the sake of `binary_op()` + let new = this.read_scalar(new)?; + + let old = this.atomic_compare_exchange_scalar( + &place, + &expect_old, + new, + success, + fail, + can_fail_spuriously, + )?; + + // Return old value. + this.write_immediate(old, dest)?; + Ok(()) + } + + fn atomic_compare_exchange( + &mut self, + args: &[OpTy<'tcx, Provenance>], + dest: &PlaceTy<'tcx, Provenance>, + success: AtomicRwOrd, + fail: AtomicReadOrd, + ) -> InterpResult<'tcx> { + self.atomic_compare_exchange_impl(args, dest, success, fail, false) + } + + fn atomic_compare_exchange_weak( + &mut self, + args: &[OpTy<'tcx, Provenance>], + dest: &PlaceTy<'tcx, Provenance>, + success: AtomicRwOrd, + fail: AtomicReadOrd, + ) -> InterpResult<'tcx> { + self.atomic_compare_exchange_impl(args, dest, success, fail, true) + } +} diff --git a/src/tools/miri/src/shims/intrinsics/mod.rs b/src/tools/miri/src/shims/intrinsics/mod.rs new file mode 100644 index 0000000000000..e0985ace5be7d --- /dev/null +++ b/src/tools/miri/src/shims/intrinsics/mod.rs @@ -0,0 +1,429 @@ +mod atomic; +mod simd; + +use std::iter; + +use log::trace; + +use rustc_apfloat::{Float, Round}; +use rustc_middle::ty::layout::{IntegerExt, LayoutOf}; +use rustc_middle::{ + mir, + ty::{self, FloatTy, Ty}, +}; +use rustc_target::abi::Integer; + +use crate::*; +use atomic::EvalContextExt as _; +use helpers::check_arg_count; +use simd::EvalContextExt as _; + +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + fn call_intrinsic( + &mut self, + instance: ty::Instance<'tcx>, + args: &[OpTy<'tcx, Provenance>], + dest: &PlaceTy<'tcx, Provenance>, + ret: Option, + _unwind: StackPopUnwind, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + + // See if the core engine can handle this intrinsic. + if this.emulate_intrinsic(instance, args, dest, ret)? { + return Ok(()); + } + + // All remaining supported intrinsics have a return place. + let intrinsic_name = this.tcx.item_name(instance.def_id()); + let intrinsic_name = intrinsic_name.as_str(); + let ret = match ret { + None => throw_unsup_format!("unimplemented (diverging) intrinsic: `{intrinsic_name}`"), + Some(p) => p, + }; + + // Some intrinsics are special and need the "ret". + match intrinsic_name { + "try" => return this.handle_try(args, dest, ret), + _ => {} + } + + // The rest jumps to `ret` immediately. + this.emulate_intrinsic_by_name(intrinsic_name, args, dest)?; + + trace!("{:?}", this.dump_place(**dest)); + this.go_to_block(ret); + Ok(()) + } + + /// Emulates a Miri-supported intrinsic (not supported by the core engine). + fn emulate_intrinsic_by_name( + &mut self, + intrinsic_name: &str, + args: &[OpTy<'tcx, Provenance>], + dest: &PlaceTy<'tcx, Provenance>, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + + if let Some(name) = intrinsic_name.strip_prefix("atomic_") { + return this.emulate_atomic_intrinsic(name, args, dest); + } + if let Some(name) = intrinsic_name.strip_prefix("simd_") { + return this.emulate_simd_intrinsic(name, args, dest); + } + + match intrinsic_name { + // Miri overwriting CTFE intrinsics. + "ptr_guaranteed_cmp" => { + let [left, right] = check_arg_count(args)?; + let left = this.read_immediate(left)?; + let right = this.read_immediate(right)?; + let (val, _overflowed, _ty) = + this.overflowing_binary_op(mir::BinOp::Eq, &left, &right)?; + // We're type punning a bool as an u8 here. + this.write_scalar(val, dest)?; + } + "const_allocate" => { + // For now, for compatibility with the run-time implementation of this, we just return null. + // See . + this.write_null(dest)?; + } + "const_deallocate" => { + // complete NOP + } + + // Raw memory accesses + "volatile_load" => { + let [place] = check_arg_count(args)?; + let place = this.deref_operand(place)?; + this.copy_op(&place.into(), dest, /*allow_transmute*/ false)?; + } + "volatile_store" => { + let [place, dest] = check_arg_count(args)?; + let place = this.deref_operand(place)?; + this.copy_op(dest, &place.into(), /*allow_transmute*/ false)?; + } + + "write_bytes" | "volatile_set_memory" => { + let [ptr, val_byte, count] = check_arg_count(args)?; + let ty = ptr.layout.ty.builtin_deref(true).unwrap().ty; + let ty_layout = this.layout_of(ty)?; + let val_byte = this.read_scalar(val_byte)?.to_u8()?; + let ptr = this.read_pointer(ptr)?; + let count = this.read_scalar(count)?.to_machine_usize(this)?; + // `checked_mul` enforces a too small bound (the correct one would probably be machine_isize_max), + // but no actual allocation can be big enough for the difference to be noticeable. + let byte_count = ty_layout.size.checked_mul(count, this).ok_or_else(|| { + err_ub_format!("overflow computing total size of `{intrinsic_name}`") + })?; + this.write_bytes_ptr(ptr, iter::repeat(val_byte).take(byte_count.bytes_usize()))?; + } + + // Floating-point operations + "fabsf32" => { + let [f] = check_arg_count(args)?; + let f = this.read_scalar(f)?.to_f32()?; + // Can be implemented in soft-floats. + this.write_scalar(Scalar::from_f32(f.abs()), dest)?; + } + "fabsf64" => { + let [f] = check_arg_count(args)?; + let f = this.read_scalar(f)?.to_f64()?; + // Can be implemented in soft-floats. + this.write_scalar(Scalar::from_f64(f.abs()), dest)?; + } + #[rustfmt::skip] + | "sinf32" + | "cosf32" + | "sqrtf32" + | "expf32" + | "exp2f32" + | "logf32" + | "log10f32" + | "log2f32" + | "floorf32" + | "ceilf32" + | "truncf32" + | "roundf32" + => { + let [f] = check_arg_count(args)?; + // FIXME: Using host floats. + let f = f32::from_bits(this.read_scalar(f)?.to_u32()?); + let f = match intrinsic_name { + "sinf32" => f.sin(), + "cosf32" => f.cos(), + "sqrtf32" => f.sqrt(), + "expf32" => f.exp(), + "exp2f32" => f.exp2(), + "logf32" => f.ln(), + "log10f32" => f.log10(), + "log2f32" => f.log2(), + "floorf32" => f.floor(), + "ceilf32" => f.ceil(), + "truncf32" => f.trunc(), + "roundf32" => f.round(), + _ => bug!(), + }; + this.write_scalar(Scalar::from_u32(f.to_bits()), dest)?; + } + + #[rustfmt::skip] + | "sinf64" + | "cosf64" + | "sqrtf64" + | "expf64" + | "exp2f64" + | "logf64" + | "log10f64" + | "log2f64" + | "floorf64" + | "ceilf64" + | "truncf64" + | "roundf64" + => { + let [f] = check_arg_count(args)?; + // FIXME: Using host floats. + let f = f64::from_bits(this.read_scalar(f)?.to_u64()?); + let f = match intrinsic_name { + "sinf64" => f.sin(), + "cosf64" => f.cos(), + "sqrtf64" => f.sqrt(), + "expf64" => f.exp(), + "exp2f64" => f.exp2(), + "logf64" => f.ln(), + "log10f64" => f.log10(), + "log2f64" => f.log2(), + "floorf64" => f.floor(), + "ceilf64" => f.ceil(), + "truncf64" => f.trunc(), + "roundf64" => f.round(), + _ => bug!(), + }; + this.write_scalar(Scalar::from_u64(f.to_bits()), dest)?; + } + + #[rustfmt::skip] + | "fadd_fast" + | "fsub_fast" + | "fmul_fast" + | "fdiv_fast" + | "frem_fast" + => { + let [a, b] = check_arg_count(args)?; + let a = this.read_immediate(a)?; + let b = this.read_immediate(b)?; + let op = match intrinsic_name { + "fadd_fast" => mir::BinOp::Add, + "fsub_fast" => mir::BinOp::Sub, + "fmul_fast" => mir::BinOp::Mul, + "fdiv_fast" => mir::BinOp::Div, + "frem_fast" => mir::BinOp::Rem, + _ => bug!(), + }; + let float_finite = |x: &ImmTy<'tcx, _>| -> InterpResult<'tcx, bool> { + Ok(match x.layout.ty.kind() { + ty::Float(FloatTy::F32) => x.to_scalar().to_f32()?.is_finite(), + ty::Float(FloatTy::F64) => x.to_scalar().to_f64()?.is_finite(), + _ => bug!( + "`{intrinsic_name}` called with non-float input type {ty:?}", + ty = x.layout.ty, + ), + }) + }; + match (float_finite(&a)?, float_finite(&b)?) { + (false, false) => throw_ub_format!( + "`{intrinsic_name}` intrinsic called with non-finite value as both parameters", + ), + (false, _) => throw_ub_format!( + "`{intrinsic_name}` intrinsic called with non-finite value as first parameter", + ), + (_, false) => throw_ub_format!( + "`{intrinsic_name}` intrinsic called with non-finite value as second parameter", + ), + _ => {} + } + this.binop_ignore_overflow(op, &a, &b, dest)?; + } + + #[rustfmt::skip] + | "minnumf32" + | "maxnumf32" + | "copysignf32" + => { + let [a, b] = check_arg_count(args)?; + let a = this.read_scalar(a)?.to_f32()?; + let b = this.read_scalar(b)?.to_f32()?; + let res = match intrinsic_name { + "minnumf32" => a.min(b), + "maxnumf32" => a.max(b), + "copysignf32" => a.copy_sign(b), + _ => bug!(), + }; + this.write_scalar(Scalar::from_f32(res), dest)?; + } + + #[rustfmt::skip] + | "minnumf64" + | "maxnumf64" + | "copysignf64" + => { + let [a, b] = check_arg_count(args)?; + let a = this.read_scalar(a)?.to_f64()?; + let b = this.read_scalar(b)?.to_f64()?; + let res = match intrinsic_name { + "minnumf64" => a.min(b), + "maxnumf64" => a.max(b), + "copysignf64" => a.copy_sign(b), + _ => bug!(), + }; + this.write_scalar(Scalar::from_f64(res), dest)?; + } + + "powf32" => { + let [f, f2] = check_arg_count(args)?; + // FIXME: Using host floats. + let f = f32::from_bits(this.read_scalar(f)?.to_u32()?); + let f2 = f32::from_bits(this.read_scalar(f2)?.to_u32()?); + let res = f.powf(f2); + this.write_scalar(Scalar::from_u32(res.to_bits()), dest)?; + } + + "powf64" => { + let [f, f2] = check_arg_count(args)?; + // FIXME: Using host floats. + let f = f64::from_bits(this.read_scalar(f)?.to_u64()?); + let f2 = f64::from_bits(this.read_scalar(f2)?.to_u64()?); + let res = f.powf(f2); + this.write_scalar(Scalar::from_u64(res.to_bits()), dest)?; + } + + "fmaf32" => { + let [a, b, c] = check_arg_count(args)?; + // FIXME: Using host floats, to work around https://github.com/rust-lang/miri/issues/2468. + let a = f32::from_bits(this.read_scalar(a)?.to_u32()?); + let b = f32::from_bits(this.read_scalar(b)?.to_u32()?); + let c = f32::from_bits(this.read_scalar(c)?.to_u32()?); + let res = a.mul_add(b, c); + this.write_scalar(Scalar::from_u32(res.to_bits()), dest)?; + } + + "fmaf64" => { + let [a, b, c] = check_arg_count(args)?; + // FIXME: Using host floats, to work around https://github.com/rust-lang/miri/issues/2468. + let a = f64::from_bits(this.read_scalar(a)?.to_u64()?); + let b = f64::from_bits(this.read_scalar(b)?.to_u64()?); + let c = f64::from_bits(this.read_scalar(c)?.to_u64()?); + let res = a.mul_add(b, c); + this.write_scalar(Scalar::from_u64(res.to_bits()), dest)?; + } + + "powif32" => { + let [f, i] = check_arg_count(args)?; + // FIXME: Using host floats. + let f = f32::from_bits(this.read_scalar(f)?.to_u32()?); + let i = this.read_scalar(i)?.to_i32()?; + let res = f.powi(i); + this.write_scalar(Scalar::from_u32(res.to_bits()), dest)?; + } + + "powif64" => { + let [f, i] = check_arg_count(args)?; + // FIXME: Using host floats. + let f = f64::from_bits(this.read_scalar(f)?.to_u64()?); + let i = this.read_scalar(i)?.to_i32()?; + let res = f.powi(i); + this.write_scalar(Scalar::from_u64(res.to_bits()), dest)?; + } + + "float_to_int_unchecked" => { + let [val] = check_arg_count(args)?; + let val = this.read_immediate(val)?; + + let res = match val.layout.ty.kind() { + ty::Float(FloatTy::F32) => + this.float_to_int_unchecked(val.to_scalar().to_f32()?, dest.layout.ty)?, + ty::Float(FloatTy::F64) => + this.float_to_int_unchecked(val.to_scalar().to_f64()?, dest.layout.ty)?, + _ => + span_bug!( + this.cur_span(), + "`float_to_int_unchecked` called with non-float input type {:?}", + val.layout.ty + ), + }; + + this.write_scalar(res, dest)?; + } + + // Other + "exact_div" => { + let [num, denom] = check_arg_count(args)?; + this.exact_div(&this.read_immediate(num)?, &this.read_immediate(denom)?, dest)?; + } + + "breakpoint" => { + let [] = check_arg_count(args)?; + // normally this would raise a SIGTRAP, which aborts if no debugger is connected + throw_machine_stop!(TerminationInfo::Abort(format!("Trace/breakpoint trap"))) + } + + name => throw_unsup_format!("unimplemented intrinsic: `{name}`"), + } + + Ok(()) + } + + fn float_to_int_unchecked( + &self, + f: F, + dest_ty: Ty<'tcx>, + ) -> InterpResult<'tcx, Scalar> + where + F: Float + Into>, + { + let this = self.eval_context_ref(); + + // Step 1: cut off the fractional part of `f`. The result of this is + // guaranteed to be precisely representable in IEEE floats. + let f = f.round_to_integral(Round::TowardZero).value; + + // Step 2: Cast the truncated float to the target integer type and see if we lose any information in this step. + Ok(match dest_ty.kind() { + // Unsigned + ty::Uint(t) => { + let size = Integer::from_uint_ty(this, *t).size(); + let res = f.to_u128(size.bits_usize()); + if res.status.is_empty() { + // No status flags means there was no further rounding or other loss of precision. + Scalar::from_uint(res.value, size) + } else { + // `f` was not representable in this integer type. + throw_ub_format!( + "`float_to_int_unchecked` intrinsic called on {f} which cannot be represented in target type `{dest_ty:?}`", + ); + } + } + // Signed + ty::Int(t) => { + let size = Integer::from_int_ty(this, *t).size(); + let res = f.to_i128(size.bits_usize()); + if res.status.is_empty() { + // No status flags means there was no further rounding or other loss of precision. + Scalar::from_int(res.value, size) + } else { + // `f` was not representable in this integer type. + throw_ub_format!( + "`float_to_int_unchecked` intrinsic called on {f} which cannot be represented in target type `{dest_ty:?}`", + ); + } + } + // Nothing else + _ => + span_bug!( + this.cur_span(), + "`float_to_int_unchecked` called with non-int output type {dest_ty:?}" + ), + }) + } +} diff --git a/src/tools/miri/src/shims/intrinsics/simd.rs b/src/tools/miri/src/shims/intrinsics/simd.rs new file mode 100644 index 0000000000000..163d185f66f3a --- /dev/null +++ b/src/tools/miri/src/shims/intrinsics/simd.rs @@ -0,0 +1,627 @@ +use rustc_apfloat::Float; +use rustc_middle::ty::layout::{HasParamEnv, LayoutOf}; +use rustc_middle::{mir, ty, ty::FloatTy}; +use rustc_target::abi::{Endian, HasDataLayout, Size}; + +use crate::*; +use helpers::check_arg_count; + +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + /// Calls the simd intrinsic `intrinsic`; the `simd_` prefix has already been removed. + fn emulate_simd_intrinsic( + &mut self, + intrinsic_name: &str, + args: &[OpTy<'tcx, Provenance>], + dest: &PlaceTy<'tcx, Provenance>, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + match intrinsic_name { + #[rustfmt::skip] + | "neg" + | "fabs" + | "ceil" + | "floor" + | "round" + | "trunc" + | "fsqrt" => { + let [op] = check_arg_count(args)?; + let (op, op_len) = this.operand_to_simd(op)?; + let (dest, dest_len) = this.place_to_simd(dest)?; + + assert_eq!(dest_len, op_len); + + #[derive(Copy, Clone)] + enum HostFloatOp { + Ceil, + Floor, + Round, + Trunc, + Sqrt, + } + #[derive(Copy, Clone)] + enum Op { + MirOp(mir::UnOp), + Abs, + HostOp(HostFloatOp), + } + let which = match intrinsic_name { + "neg" => Op::MirOp(mir::UnOp::Neg), + "fabs" => Op::Abs, + "ceil" => Op::HostOp(HostFloatOp::Ceil), + "floor" => Op::HostOp(HostFloatOp::Floor), + "round" => Op::HostOp(HostFloatOp::Round), + "trunc" => Op::HostOp(HostFloatOp::Trunc), + "fsqrt" => Op::HostOp(HostFloatOp::Sqrt), + _ => unreachable!(), + }; + + for i in 0..dest_len { + let op = this.read_immediate(&this.mplace_index(&op, i)?.into())?; + let dest = this.mplace_index(&dest, i)?; + let val = match which { + Op::MirOp(mir_op) => this.unary_op(mir_op, &op)?.to_scalar(), + Op::Abs => { + // Works for f32 and f64. + let ty::Float(float_ty) = op.layout.ty.kind() else { + span_bug!(this.cur_span(), "{} operand is not a float", intrinsic_name) + }; + let op = op.to_scalar(); + match float_ty { + FloatTy::F32 => Scalar::from_f32(op.to_f32()?.abs()), + FloatTy::F64 => Scalar::from_f64(op.to_f64()?.abs()), + } + } + Op::HostOp(host_op) => { + let ty::Float(float_ty) = op.layout.ty.kind() else { + span_bug!(this.cur_span(), "{} operand is not a float", intrinsic_name) + }; + // FIXME using host floats + match float_ty { + FloatTy::F32 => { + let f = f32::from_bits(op.to_scalar().to_u32()?); + let res = match host_op { + HostFloatOp::Ceil => f.ceil(), + HostFloatOp::Floor => f.floor(), + HostFloatOp::Round => f.round(), + HostFloatOp::Trunc => f.trunc(), + HostFloatOp::Sqrt => f.sqrt(), + }; + Scalar::from_u32(res.to_bits()) + } + FloatTy::F64 => { + let f = f64::from_bits(op.to_scalar().to_u64()?); + let res = match host_op { + HostFloatOp::Ceil => f.ceil(), + HostFloatOp::Floor => f.floor(), + HostFloatOp::Round => f.round(), + HostFloatOp::Trunc => f.trunc(), + HostFloatOp::Sqrt => f.sqrt(), + }; + Scalar::from_u64(res.to_bits()) + } + } + + } + }; + this.write_scalar(val, &dest.into())?; + } + } + #[rustfmt::skip] + | "add" + | "sub" + | "mul" + | "div" + | "rem" + | "shl" + | "shr" + | "and" + | "or" + | "xor" + | "eq" + | "ne" + | "lt" + | "le" + | "gt" + | "ge" + | "fmax" + | "fmin" + | "saturating_add" + | "saturating_sub" + | "arith_offset" => { + use mir::BinOp; + + let [left, right] = check_arg_count(args)?; + let (left, left_len) = this.operand_to_simd(left)?; + let (right, right_len) = this.operand_to_simd(right)?; + let (dest, dest_len) = this.place_to_simd(dest)?; + + assert_eq!(dest_len, left_len); + assert_eq!(dest_len, right_len); + + enum Op { + MirOp(BinOp), + SaturatingOp(BinOp), + FMax, + FMin, + WrappingOffset, + } + let which = match intrinsic_name { + "add" => Op::MirOp(BinOp::Add), + "sub" => Op::MirOp(BinOp::Sub), + "mul" => Op::MirOp(BinOp::Mul), + "div" => Op::MirOp(BinOp::Div), + "rem" => Op::MirOp(BinOp::Rem), + "shl" => Op::MirOp(BinOp::Shl), + "shr" => Op::MirOp(BinOp::Shr), + "and" => Op::MirOp(BinOp::BitAnd), + "or" => Op::MirOp(BinOp::BitOr), + "xor" => Op::MirOp(BinOp::BitXor), + "eq" => Op::MirOp(BinOp::Eq), + "ne" => Op::MirOp(BinOp::Ne), + "lt" => Op::MirOp(BinOp::Lt), + "le" => Op::MirOp(BinOp::Le), + "gt" => Op::MirOp(BinOp::Gt), + "ge" => Op::MirOp(BinOp::Ge), + "fmax" => Op::FMax, + "fmin" => Op::FMin, + "saturating_add" => Op::SaturatingOp(BinOp::Add), + "saturating_sub" => Op::SaturatingOp(BinOp::Sub), + "arith_offset" => Op::WrappingOffset, + _ => unreachable!(), + }; + + for i in 0..dest_len { + let left = this.read_immediate(&this.mplace_index(&left, i)?.into())?; + let right = this.read_immediate(&this.mplace_index(&right, i)?.into())?; + let dest = this.mplace_index(&dest, i)?; + let val = match which { + Op::MirOp(mir_op) => { + let (val, overflowed, ty) = this.overflowing_binary_op(mir_op, &left, &right)?; + if matches!(mir_op, BinOp::Shl | BinOp::Shr) { + // Shifts have extra UB as SIMD operations that the MIR binop does not have. + // See . + if overflowed { + let r_val = right.to_scalar().to_bits(right.layout.size)?; + throw_ub_format!("overflowing shift by {r_val} in `simd_{intrinsic_name}` in SIMD lane {i}"); + } + } + if matches!(mir_op, BinOp::Eq | BinOp::Ne | BinOp::Lt | BinOp::Le | BinOp::Gt | BinOp::Ge) { + // Special handling for boolean-returning operations + assert_eq!(ty, this.tcx.types.bool); + let val = val.to_bool().unwrap(); + bool_to_simd_element(val, dest.layout.size) + } else { + assert_ne!(ty, this.tcx.types.bool); + assert_eq!(ty, dest.layout.ty); + val + } + } + Op::SaturatingOp(mir_op) => { + this.saturating_arith(mir_op, &left, &right)? + } + Op::WrappingOffset => { + let ptr = left.to_scalar().to_pointer(this)?; + let offset_count = right.to_scalar().to_machine_isize(this)?; + let pointee_ty = left.layout.ty.builtin_deref(true).unwrap().ty; + + let pointee_size = i64::try_from(this.layout_of(pointee_ty)?.size.bytes()).unwrap(); + let offset_bytes = offset_count.wrapping_mul(pointee_size); + let offset_ptr = ptr.wrapping_signed_offset(offset_bytes, this); + Scalar::from_maybe_pointer(offset_ptr, this) + } + Op::FMax => { + fmax_op(&left, &right)? + } + Op::FMin => { + fmin_op(&left, &right)? + } + }; + this.write_scalar(val, &dest.into())?; + } + } + "fma" => { + let [a, b, c] = check_arg_count(args)?; + let (a, a_len) = this.operand_to_simd(a)?; + let (b, b_len) = this.operand_to_simd(b)?; + let (c, c_len) = this.operand_to_simd(c)?; + let (dest, dest_len) = this.place_to_simd(dest)?; + + assert_eq!(dest_len, a_len); + assert_eq!(dest_len, b_len); + assert_eq!(dest_len, c_len); + + for i in 0..dest_len { + let a = this.read_scalar(&this.mplace_index(&a, i)?.into())?; + let b = this.read_scalar(&this.mplace_index(&b, i)?.into())?; + let c = this.read_scalar(&this.mplace_index(&c, i)?.into())?; + let dest = this.mplace_index(&dest, i)?; + + // Works for f32 and f64. + // FIXME: using host floats to work around https://github.com/rust-lang/miri/issues/2468. + let ty::Float(float_ty) = dest.layout.ty.kind() else { + span_bug!(this.cur_span(), "{} operand is not a float", intrinsic_name) + }; + let val = match float_ty { + FloatTy::F32 => { + let a = f32::from_bits(a.to_u32()?); + let b = f32::from_bits(b.to_u32()?); + let c = f32::from_bits(c.to_u32()?); + let res = a.mul_add(b, c); + Scalar::from_u32(res.to_bits()) + } + FloatTy::F64 => { + let a = f64::from_bits(a.to_u64()?); + let b = f64::from_bits(b.to_u64()?); + let c = f64::from_bits(c.to_u64()?); + let res = a.mul_add(b, c); + Scalar::from_u64(res.to_bits()) + } + }; + this.write_scalar(val, &dest.into())?; + } + } + #[rustfmt::skip] + | "reduce_and" + | "reduce_or" + | "reduce_xor" + | "reduce_any" + | "reduce_all" + | "reduce_max" + | "reduce_min" => { + use mir::BinOp; + + let [op] = check_arg_count(args)?; + let (op, op_len) = this.operand_to_simd(op)?; + + let imm_from_bool = + |b| ImmTy::from_scalar(Scalar::from_bool(b), this.machine.layouts.bool); + + enum Op { + MirOp(BinOp), + MirOpBool(BinOp), + Max, + Min, + } + let which = match intrinsic_name { + "reduce_and" => Op::MirOp(BinOp::BitAnd), + "reduce_or" => Op::MirOp(BinOp::BitOr), + "reduce_xor" => Op::MirOp(BinOp::BitXor), + "reduce_any" => Op::MirOpBool(BinOp::BitOr), + "reduce_all" => Op::MirOpBool(BinOp::BitAnd), + "reduce_max" => Op::Max, + "reduce_min" => Op::Min, + _ => unreachable!(), + }; + + // Initialize with first lane, then proceed with the rest. + let mut res = this.read_immediate(&this.mplace_index(&op, 0)?.into())?; + if matches!(which, Op::MirOpBool(_)) { + // Convert to `bool` scalar. + res = imm_from_bool(simd_element_to_bool(res)?); + } + for i in 1..op_len { + let op = this.read_immediate(&this.mplace_index(&op, i)?.into())?; + res = match which { + Op::MirOp(mir_op) => { + this.binary_op(mir_op, &res, &op)? + } + Op::MirOpBool(mir_op) => { + let op = imm_from_bool(simd_element_to_bool(op)?); + this.binary_op(mir_op, &res, &op)? + } + Op::Max => { + if matches!(res.layout.ty.kind(), ty::Float(_)) { + ImmTy::from_scalar(fmax_op(&res, &op)?, res.layout) + } else { + // Just boring integers, so NaNs to worry about + if this.binary_op(BinOp::Ge, &res, &op)?.to_scalar().to_bool()? { + res + } else { + op + } + } + } + Op::Min => { + if matches!(res.layout.ty.kind(), ty::Float(_)) { + ImmTy::from_scalar(fmin_op(&res, &op)?, res.layout) + } else { + // Just boring integers, so NaNs to worry about + if this.binary_op(BinOp::Le, &res, &op)?.to_scalar().to_bool()? { + res + } else { + op + } + } + } + }; + } + this.write_immediate(*res, dest)?; + } + #[rustfmt::skip] + | "reduce_add_ordered" + | "reduce_mul_ordered" => { + use mir::BinOp; + + let [op, init] = check_arg_count(args)?; + let (op, op_len) = this.operand_to_simd(op)?; + let init = this.read_immediate(init)?; + + let mir_op = match intrinsic_name { + "reduce_add_ordered" => BinOp::Add, + "reduce_mul_ordered" => BinOp::Mul, + _ => unreachable!(), + }; + + let mut res = init; + for i in 0..op_len { + let op = this.read_immediate(&this.mplace_index(&op, i)?.into())?; + res = this.binary_op(mir_op, &res, &op)?; + } + this.write_immediate(*res, dest)?; + } + "select" => { + let [mask, yes, no] = check_arg_count(args)?; + let (mask, mask_len) = this.operand_to_simd(mask)?; + let (yes, yes_len) = this.operand_to_simd(yes)?; + let (no, no_len) = this.operand_to_simd(no)?; + let (dest, dest_len) = this.place_to_simd(dest)?; + + assert_eq!(dest_len, mask_len); + assert_eq!(dest_len, yes_len); + assert_eq!(dest_len, no_len); + + for i in 0..dest_len { + let mask = this.read_immediate(&this.mplace_index(&mask, i)?.into())?; + let yes = this.read_immediate(&this.mplace_index(&yes, i)?.into())?; + let no = this.read_immediate(&this.mplace_index(&no, i)?.into())?; + let dest = this.mplace_index(&dest, i)?; + + let val = if simd_element_to_bool(mask)? { yes } else { no }; + this.write_immediate(*val, &dest.into())?; + } + } + "select_bitmask" => { + let [mask, yes, no] = check_arg_count(args)?; + let (yes, yes_len) = this.operand_to_simd(yes)?; + let (no, no_len) = this.operand_to_simd(no)?; + let (dest, dest_len) = this.place_to_simd(dest)?; + let bitmask_len = dest_len.max(8); + + assert!(mask.layout.ty.is_integral()); + assert!(bitmask_len <= 64); + assert_eq!(bitmask_len, mask.layout.size.bits()); + assert_eq!(dest_len, yes_len); + assert_eq!(dest_len, no_len); + let dest_len = u32::try_from(dest_len).unwrap(); + let bitmask_len = u32::try_from(bitmask_len).unwrap(); + + let mask: u64 = + this.read_scalar(mask)?.to_bits(mask.layout.size)?.try_into().unwrap(); + for i in 0..dest_len { + let mask = mask + & 1u64 + .checked_shl(simd_bitmask_index(i, dest_len, this.data_layout().endian)) + .unwrap(); + let yes = this.read_immediate(&this.mplace_index(&yes, i.into())?.into())?; + let no = this.read_immediate(&this.mplace_index(&no, i.into())?.into())?; + let dest = this.mplace_index(&dest, i.into())?; + + let val = if mask != 0 { yes } else { no }; + this.write_immediate(*val, &dest.into())?; + } + for i in dest_len..bitmask_len { + // If the mask is "padded", ensure that padding is all-zero. + let mask = mask & 1u64.checked_shl(i).unwrap(); + if mask != 0 { + throw_ub_format!( + "a SIMD bitmask less than 8 bits long must be filled with 0s for the remaining bits" + ); + } + } + } + #[rustfmt::skip] + "cast" | "as" => { + let [op] = check_arg_count(args)?; + let (op, op_len) = this.operand_to_simd(op)?; + let (dest, dest_len) = this.place_to_simd(dest)?; + + assert_eq!(dest_len, op_len); + + let safe_cast = intrinsic_name == "as"; + + for i in 0..dest_len { + let op = this.read_immediate(&this.mplace_index(&op, i)?.into())?; + let dest = this.mplace_index(&dest, i)?; + + let val = match (op.layout.ty.kind(), dest.layout.ty.kind()) { + // Int-to-(int|float): always safe + (ty::Int(_) | ty::Uint(_), ty::Int(_) | ty::Uint(_) | ty::Float(_)) => + this.misc_cast(&op, dest.layout.ty)?, + // Float-to-float: always safe + (ty::Float(_), ty::Float(_)) => + this.misc_cast(&op, dest.layout.ty)?, + // Float-to-int in safe mode + (ty::Float(_), ty::Int(_) | ty::Uint(_)) if safe_cast => + this.misc_cast(&op, dest.layout.ty)?, + // Float-to-int in unchecked mode + (ty::Float(FloatTy::F32), ty::Int(_) | ty::Uint(_)) if !safe_cast => + this.float_to_int_unchecked(op.to_scalar().to_f32()?, dest.layout.ty)?.into(), + (ty::Float(FloatTy::F64), ty::Int(_) | ty::Uint(_)) if !safe_cast => + this.float_to_int_unchecked(op.to_scalar().to_f64()?, dest.layout.ty)?.into(), + _ => + throw_unsup_format!( + "Unsupported SIMD cast from element type {from_ty} to {to_ty}", + from_ty = op.layout.ty, + to_ty = dest.layout.ty, + ), + }; + this.write_immediate(val, &dest.into())?; + } + } + "shuffle" => { + let [left, right, index] = check_arg_count(args)?; + let (left, left_len) = this.operand_to_simd(left)?; + let (right, right_len) = this.operand_to_simd(right)?; + let (dest, dest_len) = this.place_to_simd(dest)?; + + // `index` is an array, not a SIMD type + let ty::Array(_, index_len) = index.layout.ty.kind() else { + span_bug!(this.cur_span(), "simd_shuffle index argument has non-array type {}", index.layout.ty) + }; + let index_len = index_len.eval_usize(*this.tcx, this.param_env()); + + assert_eq!(left_len, right_len); + assert_eq!(index_len, dest_len); + + for i in 0..dest_len { + let src_index: u64 = this + .read_immediate(&this.operand_index(index, i)?)? + .to_scalar() + .to_u32()? + .into(); + let dest = this.mplace_index(&dest, i)?; + + let val = if src_index < left_len { + this.read_immediate(&this.mplace_index(&left, src_index)?.into())? + } else if src_index < left_len.checked_add(right_len).unwrap() { + let right_idx = src_index.checked_sub(left_len).unwrap(); + this.read_immediate(&this.mplace_index(&right, right_idx)?.into())? + } else { + span_bug!( + this.cur_span(), + "simd_shuffle index {src_index} is out of bounds for 2 vectors of size {left_len}", + ); + }; + this.write_immediate(*val, &dest.into())?; + } + } + "gather" => { + let [passthru, ptrs, mask] = check_arg_count(args)?; + let (passthru, passthru_len) = this.operand_to_simd(passthru)?; + let (ptrs, ptrs_len) = this.operand_to_simd(ptrs)?; + let (mask, mask_len) = this.operand_to_simd(mask)?; + let (dest, dest_len) = this.place_to_simd(dest)?; + + assert_eq!(dest_len, passthru_len); + assert_eq!(dest_len, ptrs_len); + assert_eq!(dest_len, mask_len); + + for i in 0..dest_len { + let passthru = this.read_immediate(&this.mplace_index(&passthru, i)?.into())?; + let ptr = this.read_immediate(&this.mplace_index(&ptrs, i)?.into())?; + let mask = this.read_immediate(&this.mplace_index(&mask, i)?.into())?; + let dest = this.mplace_index(&dest, i)?; + + let val = if simd_element_to_bool(mask)? { + let place = this.deref_operand(&ptr.into())?; + this.read_immediate(&place.into())? + } else { + passthru + }; + this.write_immediate(*val, &dest.into())?; + } + } + "scatter" => { + let [value, ptrs, mask] = check_arg_count(args)?; + let (value, value_len) = this.operand_to_simd(value)?; + let (ptrs, ptrs_len) = this.operand_to_simd(ptrs)?; + let (mask, mask_len) = this.operand_to_simd(mask)?; + + assert_eq!(ptrs_len, value_len); + assert_eq!(ptrs_len, mask_len); + + for i in 0..ptrs_len { + let value = this.read_immediate(&this.mplace_index(&value, i)?.into())?; + let ptr = this.read_immediate(&this.mplace_index(&ptrs, i)?.into())?; + let mask = this.read_immediate(&this.mplace_index(&mask, i)?.into())?; + + if simd_element_to_bool(mask)? { + let place = this.deref_operand(&ptr.into())?; + this.write_immediate(*value, &place.into())?; + } + } + } + "bitmask" => { + let [op] = check_arg_count(args)?; + let (op, op_len) = this.operand_to_simd(op)?; + let bitmask_len = op_len.max(8); + + assert!(dest.layout.ty.is_integral()); + assert!(bitmask_len <= 64); + assert_eq!(bitmask_len, dest.layout.size.bits()); + let op_len = u32::try_from(op_len).unwrap(); + + let mut res = 0u64; + for i in 0..op_len { + let op = this.read_immediate(&this.mplace_index(&op, i.into())?.into())?; + if simd_element_to_bool(op)? { + res |= 1u64 + .checked_shl(simd_bitmask_index(i, op_len, this.data_layout().endian)) + .unwrap(); + } + } + this.write_int(res, dest)?; + } + + name => throw_unsup_format!("unimplemented intrinsic: `simd_{name}`"), + } + Ok(()) + } +} + +fn bool_to_simd_element(b: bool, size: Size) -> Scalar { + // SIMD uses all-1 as pattern for "true" + let val = if b { -1 } else { 0 }; + Scalar::from_int(val, size) +} + +fn simd_element_to_bool(elem: ImmTy<'_, Provenance>) -> InterpResult<'_, bool> { + let val = elem.to_scalar().to_int(elem.layout.size)?; + Ok(match val { + 0 => false, + -1 => true, + _ => throw_ub_format!("each element of a SIMD mask must be all-0-bits or all-1-bits"), + }) +} + +fn simd_bitmask_index(idx: u32, vec_len: u32, endianess: Endian) -> u32 { + assert!(idx < vec_len); + match endianess { + Endian::Little => idx, + #[allow(clippy::integer_arithmetic)] // idx < vec_len + Endian::Big => vec_len - 1 - idx, // reverse order of bits + } +} + +fn fmax_op<'tcx>( + left: &ImmTy<'tcx, Provenance>, + right: &ImmTy<'tcx, Provenance>, +) -> InterpResult<'tcx, Scalar> { + assert_eq!(left.layout.ty, right.layout.ty); + let ty::Float(float_ty) = left.layout.ty.kind() else { + bug!("fmax operand is not a float") + }; + let left = left.to_scalar(); + let right = right.to_scalar(); + Ok(match float_ty { + FloatTy::F32 => Scalar::from_f32(left.to_f32()?.max(right.to_f32()?)), + FloatTy::F64 => Scalar::from_f64(left.to_f64()?.max(right.to_f64()?)), + }) +} + +fn fmin_op<'tcx>( + left: &ImmTy<'tcx, Provenance>, + right: &ImmTy<'tcx, Provenance>, +) -> InterpResult<'tcx, Scalar> { + assert_eq!(left.layout.ty, right.layout.ty); + let ty::Float(float_ty) = left.layout.ty.kind() else { + bug!("fmin operand is not a float") + }; + let left = left.to_scalar(); + let right = right.to_scalar(); + Ok(match float_ty { + FloatTy::F32 => Scalar::from_f32(left.to_f32()?.min(right.to_f32()?)), + FloatTy::F64 => Scalar::from_f64(left.to_f64()?.min(right.to_f64()?)), + }) +} diff --git a/src/tools/miri/src/shims/mod.rs b/src/tools/miri/src/shims/mod.rs new file mode 100644 index 0000000000000..8cb648e517328 --- /dev/null +++ b/src/tools/miri/src/shims/mod.rs @@ -0,0 +1,107 @@ +#![warn(clippy::integer_arithmetic)] + +mod backtrace; +#[cfg(unix)] +pub mod ffi_support; +pub mod foreign_items; +pub mod intrinsics; +pub mod unix; +pub mod windows; + +pub mod dlsym; +pub mod env; +pub mod os_str; +pub mod panic; +pub mod time; +pub mod tls; + +// End module management, begin local code + +use log::trace; + +use rustc_middle::{mir, ty}; +use rustc_target::spec::abi::Abi; + +use crate::*; +use helpers::check_arg_count; + +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + fn find_mir_or_eval_fn( + &mut self, + instance: ty::Instance<'tcx>, + abi: Abi, + args: &[OpTy<'tcx, Provenance>], + dest: &PlaceTy<'tcx, Provenance>, + ret: Option, + unwind: StackPopUnwind, + ) -> InterpResult<'tcx, Option<(&'mir mir::Body<'tcx>, ty::Instance<'tcx>)>> { + let this = self.eval_context_mut(); + trace!("eval_fn_call: {:#?}, {:?}", instance, dest); + + // There are some more lang items we want to hook that CTFE does not hook (yet). + if this.tcx.lang_items().align_offset_fn() == Some(instance.def.def_id()) { + let [ptr, align] = check_arg_count(args)?; + if this.align_offset(ptr, align, dest, ret, unwind)? { + return Ok(None); + } + } + + // Try to see if we can do something about foreign items. + if this.tcx.is_foreign_item(instance.def_id()) { + // An external function call that does not have a MIR body. We either find MIR elsewhere + // or emulate its effect. + // This will be Ok(None) if we're emulating the intrinsic entirely within Miri (no need + // to run extra MIR), and Ok(Some(body)) if we found MIR to run for the + // foreign function + // Any needed call to `goto_block` will be performed by `emulate_foreign_item`. + return this.emulate_foreign_item(instance.def_id(), abi, args, dest, ret, unwind); + } + + // Otherwise, load the MIR. + Ok(Some((this.load_mir(instance.def, None)?, instance))) + } + + /// Returns `true` if the computation was performed, and `false` if we should just evaluate + /// the actual MIR of `align_offset`. + fn align_offset( + &mut self, + ptr_op: &OpTy<'tcx, Provenance>, + align_op: &OpTy<'tcx, Provenance>, + dest: &PlaceTy<'tcx, Provenance>, + ret: Option, + unwind: StackPopUnwind, + ) -> InterpResult<'tcx, bool> { + let this = self.eval_context_mut(); + let ret = ret.unwrap(); + + if this.machine.check_alignment != AlignmentCheck::Symbolic { + // Just use actual implementation. + return Ok(false); + } + + let req_align = this.read_scalar(align_op)?.to_machine_usize(this)?; + + // Stop if the alignment is not a power of two. + if !req_align.is_power_of_two() { + this.start_panic("align_offset: align is not a power-of-two", unwind)?; + return Ok(true); // nothing left to do + } + + let ptr = this.read_pointer(ptr_op)?; + if let Ok((alloc_id, _offset, _)) = this.ptr_try_get_alloc_id(ptr) { + // Only do anything if we can identify the allocation this goes to. + let (_size, cur_align, _kind) = this.get_alloc_info(alloc_id); + if cur_align.bytes() >= req_align { + // If the allocation alignment is at least the required alignment we use the + // real implementation. + return Ok(false); + } + } + + // Return error result (usize::MAX), and jump to caller. + this.write_scalar(Scalar::from_machine_usize(this.machine_usize_max(), this), dest)?; + this.go_to_block(ret); + Ok(true) + } +} diff --git a/src/tools/miri/src/shims/os_str.rs b/src/tools/miri/src/shims/os_str.rs new file mode 100644 index 0000000000000..407dab970ad7d --- /dev/null +++ b/src/tools/miri/src/shims/os_str.rs @@ -0,0 +1,308 @@ +use std::borrow::Cow; +use std::ffi::{OsStr, OsString}; +use std::iter; +use std::path::{Path, PathBuf}; + +#[cfg(unix)] +use std::os::unix::ffi::{OsStrExt, OsStringExt}; +#[cfg(windows)] +use std::os::windows::ffi::{OsStrExt, OsStringExt}; + +use rustc_middle::ty::layout::LayoutOf; +use rustc_target::abi::{Align, Size}; + +use crate::*; + +/// Represent how path separator conversion should be done. +pub enum PathConversion { + HostToTarget, + TargetToHost, +} + +#[cfg(unix)] +pub fn os_str_to_bytes<'a, 'tcx>(os_str: &'a OsStr) -> InterpResult<'tcx, &'a [u8]> { + Ok(os_str.as_bytes()) +} + +#[cfg(not(unix))] +pub fn os_str_to_bytes<'a, 'tcx>(os_str: &'a OsStr) -> InterpResult<'tcx, &'a [u8]> { + // 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. + 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(unix)] +pub fn bytes_to_os_str<'a, 'tcx>(bytes: &'a [u8]) -> InterpResult<'tcx, &'a OsStr> { + Ok(OsStr::from_bytes(bytes)) +} +#[cfg(not(unix))] +pub fn bytes_to_os_str<'a, 'tcx>(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)) +} + +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + /// Helper function to read an OsString from a null-terminated sequence of bytes, which is what + /// the Unix APIs usually handle. + fn read_os_str_from_c_str<'a>( + &'a self, + ptr: Pointer>, + ) -> InterpResult<'tcx, &'a OsStr> + where + 'tcx: 'a, + 'mir: 'a, + { + let this = self.eval_context_ref(); + let bytes = this.read_c_str(ptr)?; + bytes_to_os_str(bytes) + } + + /// Helper function to read an OsString from a 0x0000-terminated sequence of u16, + /// which is what the Windows APIs usually handle. + fn read_os_str_from_wide_str<'a>( + &'a self, + ptr: Pointer>, + ) -> InterpResult<'tcx, OsString> + where + 'tcx: 'a, + 'mir: 'a, + { + #[cfg(windows)] + pub fn u16vec_to_osstring<'tcx>(u16_vec: Vec) -> InterpResult<'tcx, OsString> { + Ok(OsString::from_wide(&u16_vec[..])) + } + #[cfg(not(windows))] + pub fn u16vec_to_osstring<'tcx>(u16_vec: Vec) -> InterpResult<'tcx, OsString> { + let s = String::from_utf16(&u16_vec[..]) + .map_err(|_| err_unsup_format!("{:?} is not a valid utf-16 string", u16_vec))?; + Ok(s.into()) + } + + let u16_vec = self.eval_context_ref().read_wide_str(ptr)?; + u16vec_to_osstring(u16_vec) + } + + /// Helper function to write an OsStr as a null-terminated sequence of bytes, which is what + /// the Unix 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. + fn write_os_str_to_c_str( + &mut self, + os_str: &OsStr, + ptr: Pointer>, + size: u64, + ) -> InterpResult<'tcx, (bool, u64)> { + let bytes = os_str_to_bytes(os_str)?; + // If `size` is smaller or equal than `bytes.len()`, writing `bytes` plus the required null + // terminator to memory using the `ptr` pointer would cause an out-of-bounds access. + let string_length = u64::try_from(bytes.len()).unwrap(); + let string_length = string_length.checked_add(1).unwrap(); + if size < string_length { + return Ok((false, string_length)); + } + self.eval_context_mut() + .write_bytes_ptr(ptr, bytes.iter().copied().chain(iter::once(0u8)))?; + Ok((true, string_length)) + } + + /// 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.` + fn write_os_str_to_wide_str( + &mut self, + os_str: &OsStr, + ptr: Pointer>, + size: u64, + ) -> InterpResult<'tcx, (bool, u64)> { + #[cfg(windows)] + fn os_str_to_u16vec<'tcx>(os_str: &OsStr) -> InterpResult<'tcx, Vec> { + Ok(os_str.encode_wide().collect()) + } + #[cfg(not(windows))] + fn os_str_to_u16vec<'tcx>(os_str: &OsStr) -> InterpResult<'tcx, Vec> { + // On non-Windows platforms the best we can do to transform Vec from/to OS strings is to do the + // intermediate transformation into strings. Which invalidates non-utf8 paths that are actually + // valid. + os_str + .to_str() + .map(|s| s.encode_utf16().collect()) + .ok_or_else(|| err_unsup_format!("{:?} is not a valid utf-8 string", os_str).into()) + } + + let u16_vec = os_str_to_u16vec(os_str)?; + // If `size` is smaller or equal than `bytes.len()`, writing `bytes` plus the required + // 0x0000 terminator to memory would cause an out-of-bounds access. + let string_length = u64::try_from(u16_vec.len()).unwrap(); + let string_length = string_length.checked_add(1).unwrap(); + if size < string_length { + return Ok((false, string_length)); + } + + // Store the UTF-16 string. + let size2 = Size::from_bytes(2); + let this = self.eval_context_mut(); + let mut alloc = this + .get_ptr_alloc_mut(ptr, size2 * string_length, Align::from_bytes(2).unwrap())? + .unwrap(); // not a ZST, so we will get a result + for (offset, wchar) in u16_vec.into_iter().chain(iter::once(0x0000)).enumerate() { + let offset = u64::try_from(offset).unwrap(); + alloc.write_scalar(alloc_range(size2 * offset, size2), Scalar::from_u16(wchar))?; + } + Ok((true, string_length)) + } + + /// Allocate enough memory to store the given `OsStr` as a null-terminated sequence of bytes. + fn alloc_os_str_as_c_str( + &mut self, + os_str: &OsStr, + memkind: MemoryKind, + ) -> InterpResult<'tcx, Pointer>> { + let size = u64::try_from(os_str.len()).unwrap().checked_add(1).unwrap(); // 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)?; + assert!(self.write_os_str_to_c_str(os_str, arg_place.ptr, size).unwrap().0); + Ok(arg_place.ptr) + } + + /// Allocate enough memory to store the given `OsStr` as a null-terminated sequence of `u16`. + fn alloc_os_str_as_wide_str( + &mut self, + os_str: &OsStr, + memkind: MemoryKind, + ) -> InterpResult<'tcx, Pointer>> { + let size = u64::try_from(os_str.len()).unwrap().checked_add(1).unwrap(); // Make space for `0x0000` terminator. + let this = self.eval_context_mut(); + + 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); + Ok(arg_place.ptr) + } + + /// Read a null-terminated sequence of bytes, and perform path separator conversion if needed. + fn read_path_from_c_str<'a>( + &'a self, + ptr: Pointer>, + ) -> InterpResult<'tcx, Cow<'a, Path>> + where + 'tcx: 'a, + 'mir: 'a, + { + let this = self.eval_context_ref(); + let os_str = this.read_os_str_from_c_str(ptr)?; + + Ok(match this.convert_path_separator(Cow::Borrowed(os_str), PathConversion::TargetToHost) { + Cow::Borrowed(x) => Cow::Borrowed(Path::new(x)), + Cow::Owned(y) => Cow::Owned(PathBuf::from(y)), + }) + } + + /// Read a null-terminated sequence of `u16`s, and perform path separator conversion if needed. + fn read_path_from_wide_str( + &self, + ptr: Pointer>, + ) -> InterpResult<'tcx, PathBuf> { + let this = self.eval_context_ref(); + let os_str = this.read_os_str_from_wide_str(ptr)?; + + Ok(this + .convert_path_separator(Cow::Owned(os_str), PathConversion::TargetToHost) + .into_owned() + .into()) + } + + /// Write a Path to the machine memory (as a null-terminated sequence of bytes), + /// adjusting path separators if needed. + fn write_path_to_c_str( + &mut self, + path: &Path, + ptr: Pointer>, + size: u64, + ) -> InterpResult<'tcx, (bool, u64)> { + let this = self.eval_context_mut(); + let os_str = this + .convert_path_separator(Cow::Borrowed(path.as_os_str()), PathConversion::HostToTarget); + this.write_os_str_to_c_str(&os_str, ptr, size) + } + + /// Write a Path to the machine memory (as a null-terminated sequence of `u16`s), + /// adjusting path separators if needed. + fn write_path_to_wide_str( + &mut self, + path: &Path, + ptr: Pointer>, + size: u64, + ) -> InterpResult<'tcx, (bool, u64)> { + let this = self.eval_context_mut(); + let os_str = this + .convert_path_separator(Cow::Borrowed(path.as_os_str()), PathConversion::HostToTarget); + this.write_os_str_to_wide_str(&os_str, ptr, size) + } + + /// Allocate enough memory to store a Path as a null-terminated sequence of bytes, + /// adjusting path separators if needed. + fn alloc_path_as_c_str( + &mut self, + path: &Path, + memkind: MemoryKind, + ) -> InterpResult<'tcx, Pointer>> { + let this = self.eval_context_mut(); + let os_str = this + .convert_path_separator(Cow::Borrowed(path.as_os_str()), PathConversion::HostToTarget); + this.alloc_os_str_as_c_str(&os_str, memkind) + } + + fn convert_path_separator<'a>( + &self, + os_str: Cow<'a, OsStr>, + direction: PathConversion, + ) -> Cow<'a, OsStr> { + let this = self.eval_context_ref(); + let target_os = &this.tcx.sess.target.os; + #[cfg(windows)] + return if target_os == "windows" { + // Windows-on-Windows, all fine. + os_str + } else { + // Unix target, Windows host. + let (from, to) = match direction { + PathConversion::HostToTarget => ('\\', '/'), + PathConversion::TargetToHost => ('/', '\\'), + }; + let converted = os_str + .encode_wide() + .map(|wchar| if wchar == from as u16 { to as u16 } else { wchar }) + .collect::>(); + Cow::Owned(OsString::from_wide(&converted)) + }; + #[cfg(unix)] + return if target_os == "windows" { + // Windows target, Unix host. + let (from, to) = match direction { + PathConversion::HostToTarget => ('/', '\\'), + PathConversion::TargetToHost => ('\\', '/'), + }; + let converted = os_str + .as_bytes() + .iter() + .map(|&wchar| if wchar == from as u8 { to as u8 } else { wchar }) + .collect::>(); + Cow::Owned(OsString::from_vec(converted)) + } else { + // Unix-on-Unix, all is fine. + os_str + }; + } +} diff --git a/src/tools/miri/src/shims/panic.rs b/src/tools/miri/src/shims/panic.rs new file mode 100644 index 0000000000000..2e8245acf4a68 --- /dev/null +++ b/src/tools/miri/src/shims/panic.rs @@ -0,0 +1,227 @@ +//! Panic runtime for Miri. +//! +//! The core pieces of the runtime are: +//! - An implementation of `__rust_maybe_catch_panic` that pushes the invoked stack frame with +//! some extra metadata derived from the panic-catching arguments of `__rust_maybe_catch_panic`. +//! - A hack in `libpanic_unwind` that calls the `miri_start_panic` intrinsic instead of the +//! target-native panic runtime. (This lives in the rustc repo.) +//! - An implementation of `miri_start_panic` that stores its argument (the panic payload), and then +//! immediately returns, but on the *unwind* edge (not the normal return edge), thus initiating unwinding. +//! - A hook executed each time a frame is popped, such that if the frame pushed by `__rust_maybe_catch_panic` +//! gets popped *during unwinding*, we take the panic payload and store it according to the extra +//! metadata we remembered when pushing said frame. + +use log::trace; + +use rustc_ast::Mutability; +use rustc_middle::{mir, ty}; +use rustc_span::Symbol; +use rustc_target::spec::abi::Abi; +use rustc_target::spec::PanicStrategy; + +use crate::*; +use helpers::check_arg_count; + +/// Holds all of the relevant data for when unwinding hits a `try` frame. +#[derive(Debug)] +pub struct CatchUnwindData<'tcx> { + /// The `catch_fn` callback to call in case of a panic. + catch_fn: Pointer>, + /// The `data` argument for that callback. + data: Scalar, + /// The return place from the original call to `try`. + dest: PlaceTy<'tcx, Provenance>, + /// The return block from the original call to `try`. + ret: mir::BasicBlock, +} + +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + /// Handles the special `miri_start_panic` intrinsic, which is called + /// by libpanic_unwind to delegate the actual unwinding process to Miri. + fn handle_miri_start_panic( + &mut self, + abi: Abi, + link_name: Symbol, + args: &[OpTy<'tcx, Provenance>], + unwind: StackPopUnwind, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + + trace!("miri_start_panic: {:?}", this.frame().instance); + + // Get the raw pointer stored in arg[0] (the panic payload). + let [payload] = this.check_shim(abi, Abi::Rust, link_name, args)?; + let payload = this.read_scalar(payload)?; + let thread = this.active_thread_mut(); + assert!(thread.panic_payload.is_none(), "the panic runtime should avoid double-panics"); + thread.panic_payload = Some(payload); + + // Jump to the unwind block to begin unwinding. + this.unwind_to_block(unwind)?; + Ok(()) + } + + /// Handles the `try` intrinsic, the underlying implementation of `std::panicking::try`. + fn handle_try( + &mut self, + args: &[OpTy<'tcx, Provenance>], + dest: &PlaceTy<'tcx, Provenance>, + ret: mir::BasicBlock, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + + // Signature: + // fn r#try(try_fn: fn(*mut u8), data: *mut u8, catch_fn: fn(*mut u8, *mut u8)) -> i32 + // Calls `try_fn` with `data` as argument. If that executes normally, returns 0. + // If that unwinds, calls `catch_fn` with the first argument being `data` and + // then second argument being a target-dependent `payload` (i.e. it is up to us to define + // what that is), and returns 1. + // The `payload` is passed (by libstd) to `__rust_panic_cleanup`, which is then expected to + // return a `Box`. + // In Miri, `miri_start_panic` is passed exactly that type, so we make the `payload` simply + // a pointer to `Box`. + + // Get all the arguments. + let [try_fn, data, catch_fn] = check_arg_count(args)?; + let try_fn = this.read_pointer(try_fn)?; + let data = this.read_scalar(data)?; + let catch_fn = this.read_pointer(catch_fn)?; + + // Now we make a function call, and pass `data` as first and only argument. + let f_instance = this.get_ptr_fn(try_fn)?.as_instance()?; + trace!("try_fn: {:?}", f_instance); + this.call_function( + f_instance, + Abi::Rust, + &[data.into()], + None, + // Directly return to caller. + StackPopCleanup::Goto { ret: Some(ret), unwind: StackPopUnwind::Skip }, + )?; + + // We ourselves will return `0`, eventually (will be overwritten if we catch a panic). + this.write_null(dest)?; + + // In unwind mode, we tag this frame with the extra data needed to catch unwinding. + // This lets `handle_stack_pop` (below) know that we should stop unwinding + // when we pop this frame. + if this.tcx.sess.panic_strategy() == PanicStrategy::Unwind { + this.frame_mut().extra.catch_unwind = + Some(CatchUnwindData { catch_fn, data, dest: dest.clone(), ret }); + } + + Ok(()) + } + + fn handle_stack_pop_unwind( + &mut self, + mut extra: FrameData<'tcx>, + unwinding: bool, + ) -> InterpResult<'tcx, StackPopJump> { + let this = self.eval_context_mut(); + trace!("handle_stack_pop_unwind(extra = {:?}, unwinding = {})", extra, unwinding); + + // We only care about `catch_panic` if we're unwinding - if we're doing a normal + // return, then we don't need to do anything special. + if let (true, Some(catch_unwind)) = (unwinding, extra.catch_unwind.take()) { + // We've just popped a frame that was pushed by `try`, + // and we are unwinding, so we should catch that. + trace!( + "unwinding: found catch_panic frame during unwinding: {:?}", + this.frame().instance + ); + + // We set the return value of `try` to 1, since there was a panic. + this.write_scalar(Scalar::from_i32(1), &catch_unwind.dest)?; + + // The Thread's `panic_payload` holds what was passed to `miri_start_panic`. + // This is exactly the second argument we need to pass to `catch_fn`. + let payload = this.active_thread_mut().panic_payload.take().unwrap(); + + // Push the `catch_fn` stackframe. + let f_instance = this.get_ptr_fn(catch_unwind.catch_fn)?.as_instance()?; + trace!("catch_fn: {:?}", f_instance); + this.call_function( + f_instance, + Abi::Rust, + &[catch_unwind.data.into(), payload.into()], + None, + // Directly return to caller of `try`. + StackPopCleanup::Goto { ret: Some(catch_unwind.ret), unwind: StackPopUnwind::Skip }, + )?; + + // We pushed a new stack frame, the engine should not do any jumping now! + Ok(StackPopJump::NoJump) + } else { + Ok(StackPopJump::Normal) + } + } + + /// Start a panic in the interpreter with the given message as payload. + fn start_panic(&mut self, msg: &str, unwind: StackPopUnwind) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + + // First arg: message. + let msg = this.allocate_str(msg, MiriMemoryKind::Machine.into(), Mutability::Not); + + // Call the lang item. + let panic = this.tcx.lang_items().panic_fn().unwrap(); + let panic = ty::Instance::mono(this.tcx.tcx, panic); + this.call_function( + panic, + Abi::Rust, + &[msg.to_ref(this)], + None, + StackPopCleanup::Goto { ret: None, unwind }, + ) + } + + fn assert_panic( + &mut self, + msg: &mir::AssertMessage<'tcx>, + unwind: Option, + ) -> InterpResult<'tcx> { + use rustc_middle::mir::AssertKind::*; + let this = self.eval_context_mut(); + + match msg { + BoundsCheck { index, len } => { + // Forward to `panic_bounds_check` lang item. + + // First arg: index. + let index = this.read_scalar(&this.eval_operand(index, None)?)?; + // Second arg: len. + let len = this.read_scalar(&this.eval_operand(len, None)?)?; + + // Call the lang item. + let panic_bounds_check = this.tcx.lang_items().panic_bounds_check_fn().unwrap(); + let panic_bounds_check = ty::Instance::mono(this.tcx.tcx, panic_bounds_check); + this.call_function( + panic_bounds_check, + Abi::Rust, + &[index.into(), len.into()], + None, + StackPopCleanup::Goto { + ret: None, + unwind: match unwind { + Some(cleanup) => StackPopUnwind::Cleanup(cleanup), + None => StackPopUnwind::Skip, + }, + }, + )?; + } + _ => { + // Forward everything else to `panic` lang item. + this.start_panic( + msg.description(), + match unwind { + Some(cleanup) => StackPopUnwind::Cleanup(cleanup), + None => StackPopUnwind::Skip, + }, + )?; + } + } + Ok(()) + } +} diff --git a/src/tools/miri/src/shims/time.rs b/src/tools/miri/src/shims/time.rs new file mode 100644 index 0000000000000..24fe524539396 --- /dev/null +++ b/src/tools/miri/src/shims/time.rs @@ -0,0 +1,255 @@ +use std::time::{Duration, SystemTime}; + +use crate::*; + +/// Returns the time elapsed between the provided time and the unix epoch as a `Duration`. +pub fn system_time_to_duration<'tcx>(time: &SystemTime) -> InterpResult<'tcx, Duration> { + time.duration_since(SystemTime::UNIX_EPOCH) + .map_err(|_| err_unsup_format!("times before the Unix epoch are not supported").into()) +} + +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + fn clock_gettime( + &mut self, + clk_id_op: &OpTy<'tcx, Provenance>, + tp_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, Scalar> { + // This clock support is deliberately minimal because a lot of clock types have fiddly + // properties (is it possible for Miri to be suspended independently of the host?). If you + // have a use for another clock type, please open an issue. + + let this = self.eval_context_mut(); + + this.assert_target_os("linux", "clock_gettime"); + + let clk_id = this.read_scalar(clk_id_op)?.to_i32()?; + + // Linux has two main kinds of clocks. REALTIME clocks return the actual time since the + // Unix epoch, including effects which may cause time to move backwards such as NTP. + // Linux further distinguishes regular and "coarse" clocks, but the "coarse" version + // is just specified to be "faster and less precise", so we implement both the same way. + let absolute_clocks = + [this.eval_libc_i32("CLOCK_REALTIME")?, this.eval_libc_i32("CLOCK_REALTIME_COARSE")?]; + // The second kind is MONOTONIC clocks for which 0 is an arbitrary time point, but they are + // never allowed to go backwards. We don't need to do any additonal monotonicity + // enforcement because std::time::Instant already guarantees that it is monotonic. + let relative_clocks = + [this.eval_libc_i32("CLOCK_MONOTONIC")?, this.eval_libc_i32("CLOCK_MONOTONIC_COARSE")?]; + + let duration = if absolute_clocks.contains(&clk_id) { + this.check_no_isolation("`clock_gettime` with `REALTIME` clocks")?; + system_time_to_duration(&SystemTime::now())? + } else if relative_clocks.contains(&clk_id) { + this.machine.clock.now().duration_since(this.machine.clock.anchor()) + } else { + let einval = this.eval_libc("EINVAL")?; + this.set_last_error(einval)?; + return Ok(Scalar::from_i32(-1)); + }; + + let tv_sec = duration.as_secs(); + let tv_nsec = duration.subsec_nanos(); + + this.write_int_fields(&[tv_sec.into(), tv_nsec.into()], &this.deref_operand(tp_op)?)?; + + Ok(Scalar::from_i32(0)) + } + + fn gettimeofday( + &mut self, + tv_op: &OpTy<'tcx, Provenance>, + tz_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + this.assert_target_os_is_unix("gettimeofday"); + this.check_no_isolation("`gettimeofday`")?; + + // Using tz is obsolete and should always be null + let tz = this.read_pointer(tz_op)?; + if !this.ptr_is_null(tz)? { + let einval = this.eval_libc("EINVAL")?; + this.set_last_error(einval)?; + return Ok(-1); + } + + let duration = system_time_to_duration(&SystemTime::now())?; + let tv_sec = duration.as_secs(); + let tv_usec = duration.subsec_micros(); + + this.write_int_fields(&[tv_sec.into(), tv_usec.into()], &this.deref_operand(tv_op)?)?; + + Ok(0) + } + + #[allow(non_snake_case, clippy::integer_arithmetic)] + fn GetSystemTimeAsFileTime( + &mut self, + LPFILETIME_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + + this.assert_target_os("windows", "GetSystemTimeAsFileTime"); + this.check_no_isolation("`GetSystemTimeAsFileTime`")?; + + let NANOS_PER_SEC = this.eval_windows_u64("time", "NANOS_PER_SEC")?; + let INTERVALS_PER_SEC = this.eval_windows_u64("time", "INTERVALS_PER_SEC")?; + let INTERVALS_TO_UNIX_EPOCH = this.eval_windows_u64("time", "INTERVALS_TO_UNIX_EPOCH")?; + let NANOS_PER_INTERVAL = NANOS_PER_SEC / INTERVALS_PER_SEC; + let SECONDS_TO_UNIX_EPOCH = INTERVALS_TO_UNIX_EPOCH / INTERVALS_PER_SEC; + + let duration = system_time_to_duration(&SystemTime::now())? + + Duration::from_secs(SECONDS_TO_UNIX_EPOCH); + let duration_ticks = u64::try_from(duration.as_nanos() / u128::from(NANOS_PER_INTERVAL)) + .map_err(|_| err_unsup_format!("programs running more than 2^64 Windows ticks after the Windows epoch are not supported"))?; + + let dwLowDateTime = u32::try_from(duration_ticks & 0x00000000FFFFFFFF).unwrap(); + let dwHighDateTime = u32::try_from((duration_ticks & 0xFFFFFFFF00000000) >> 32).unwrap(); + this.write_int_fields( + &[dwLowDateTime.into(), dwHighDateTime.into()], + &this.deref_operand(LPFILETIME_op)?, + )?; + + Ok(()) + } + + #[allow(non_snake_case)] + fn QueryPerformanceCounter( + &mut self, + lpPerformanceCount_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + this.assert_target_os("windows", "QueryPerformanceCounter"); + + // QueryPerformanceCounter uses a hardware counter as its basis. + // Miri will emulate a counter with a resolution of 1 nanosecond. + let duration = this.machine.clock.now().duration_since(this.machine.clock.anchor()); + let qpc = i64::try_from(duration.as_nanos()).map_err(|_| { + err_unsup_format!("programs running longer than 2^63 nanoseconds are not supported") + })?; + this.write_scalar( + Scalar::from_i64(qpc), + &this.deref_operand(lpPerformanceCount_op)?.into(), + )?; + Ok(-1) // return non-zero on success + } + + #[allow(non_snake_case)] + fn QueryPerformanceFrequency( + &mut self, + lpFrequency_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + this.assert_target_os("windows", "QueryPerformanceFrequency"); + + // Retrieves the frequency of the hardware performance counter. + // The frequency of the performance counter is fixed at system boot and + // is consistent across all processors. + // Miri emulates a "hardware" performance counter with a resolution of 1ns, + // and thus 10^9 counts per second. + this.write_scalar( + Scalar::from_i64(1_000_000_000), + &this.deref_operand(lpFrequency_op)?.into(), + )?; + Ok(-1) // Return non-zero on success + } + + fn mach_absolute_time(&self) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_ref(); + + this.assert_target_os("macos", "mach_absolute_time"); + + // This returns a u64, with time units determined dynamically by `mach_timebase_info`. + // We return plain nanoseconds. + let duration = this.machine.clock.now().duration_since(this.machine.clock.anchor()); + let res = u64::try_from(duration.as_nanos()).map_err(|_| { + err_unsup_format!("programs running longer than 2^64 nanoseconds are not supported") + })?; + Ok(Scalar::from_u64(res)) + } + + fn mach_timebase_info( + &mut self, + info_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + + this.assert_target_os("macos", "mach_timebase_info"); + + let info = this.deref_operand(info_op)?; + + // Since our emulated ticks in `mach_absolute_time` *are* nanoseconds, + // no scaling needs to happen. + let (numer, denom) = (1, 1); + this.write_int_fields(&[numer.into(), denom.into()], &info)?; + + Ok(Scalar::from_i32(0)) // KERN_SUCCESS + } + + fn nanosleep( + &mut self, + req_op: &OpTy<'tcx, Provenance>, + _rem: &OpTy<'tcx, Provenance>, // Signal handlers are not supported, so rem will never be written to. + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + this.assert_target_os_is_unix("nanosleep"); + + let duration = match this.read_timespec(&this.deref_operand(req_op)?)? { + Some(duration) => duration, + None => { + let einval = this.eval_libc("EINVAL")?; + this.set_last_error(einval)?; + return Ok(-1); + } + }; + // If adding the duration overflows, let's just sleep for an hour. Waking up early is always acceptable. + let now = this.machine.clock.now(); + let timeout_time = now + .checked_add(duration) + .unwrap_or_else(|| now.checked_add(Duration::from_secs(3600)).unwrap()); + + let active_thread = this.get_active_thread(); + this.block_thread(active_thread); + + this.register_timeout_callback( + active_thread, + Time::Monotonic(timeout_time), + Box::new(move |ecx| { + ecx.unblock_thread(active_thread); + Ok(()) + }), + ); + + Ok(0) + } + + #[allow(non_snake_case)] + fn Sleep(&mut self, timeout: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + + this.assert_target_os("windows", "Sleep"); + + let timeout_ms = this.read_scalar(timeout)?.to_u32()?; + + let duration = Duration::from_millis(timeout_ms.into()); + let timeout_time = this.machine.clock.now().checked_add(duration).unwrap(); + + let active_thread = this.get_active_thread(); + this.block_thread(active_thread); + + this.register_timeout_callback( + active_thread, + Time::Monotonic(timeout_time), + Box::new(move |ecx| { + ecx.unblock_thread(active_thread); + Ok(()) + }), + ); + + Ok(()) + } +} diff --git a/src/tools/miri/src/shims/tls.rs b/src/tools/miri/src/shims/tls.rs new file mode 100644 index 0000000000000..d93d6a16a0736 --- /dev/null +++ b/src/tools/miri/src/shims/tls.rs @@ -0,0 +1,400 @@ +//! Implement thread-local storage. + +use std::collections::btree_map::Entry as BTreeEntry; +use std::collections::hash_map::Entry as HashMapEntry; +use std::collections::BTreeMap; + +use log::trace; + +use rustc_data_structures::fx::FxHashMap; +use rustc_middle::ty; +use rustc_target::abi::{HasDataLayout, Size}; +use rustc_target::spec::abi::Abi; + +use crate::*; + +pub type TlsKey = u128; + +#[derive(Clone, Debug)] +pub struct TlsEntry<'tcx> { + /// The data for this key. None is used to represent NULL. + /// (We normalize this early to avoid having to do a NULL-ptr-test each time we access the data.) + data: BTreeMap>, + dtor: Option>, +} + +#[derive(Clone, Debug)] +struct RunningDtorsState { + /// The last TlsKey used to retrieve a TLS destructor. `None` means that we + /// have not tried to retrieve a TLS destructor yet or that we already tried + /// all keys. + last_dtor_key: Option, +} + +#[derive(Debug)] +pub struct TlsData<'tcx> { + /// The Key to use for the next thread-local allocation. + next_key: TlsKey, + + /// pthreads-style thread-local storage. + keys: BTreeMap>, + + /// A single per thread destructor of the thread local storage (that's how + /// things work on macOS) with a data argument. + macos_thread_dtors: BTreeMap, Scalar)>, + + /// State for currently running TLS dtors. If this map contains a key for a + /// specific thread, it means that we are in the "destruct" phase, during + /// which some operations are UB. + dtors_running: FxHashMap, +} + +impl<'tcx> Default for TlsData<'tcx> { + fn default() -> Self { + TlsData { + next_key: 1, // start with 1 as we must not use 0 on Windows + keys: Default::default(), + macos_thread_dtors: Default::default(), + dtors_running: Default::default(), + } + } +} + +impl<'tcx> TlsData<'tcx> { + /// Generate a new TLS key with the given destructor. + /// `max_size` determines the integer size the key has to fit in. + #[allow(clippy::integer_arithmetic)] + pub fn create_tls_key( + &mut self, + dtor: Option>, + max_size: Size, + ) -> InterpResult<'tcx, TlsKey> { + let new_key = self.next_key; + self.next_key += 1; + self.keys.try_insert(new_key, TlsEntry { data: Default::default(), dtor }).unwrap(); + trace!("New TLS key allocated: {} with dtor {:?}", new_key, dtor); + + if max_size.bits() < 128 && new_key >= (1u128 << max_size.bits()) { + throw_unsup_format!("we ran out of TLS key space"); + } + Ok(new_key) + } + + pub fn delete_tls_key(&mut self, key: TlsKey) -> InterpResult<'tcx> { + match self.keys.remove(&key) { + Some(_) => { + trace!("TLS key {} removed", key); + Ok(()) + } + None => throw_ub_format!("removing a non-existig TLS key: {}", key), + } + } + + pub fn load_tls( + &self, + key: TlsKey, + thread_id: ThreadId, + cx: &impl HasDataLayout, + ) -> InterpResult<'tcx, Scalar> { + match self.keys.get(&key) { + Some(TlsEntry { data, .. }) => { + let value = data.get(&thread_id).copied(); + trace!("TLS key {} for thread {:?} loaded: {:?}", key, thread_id, value); + Ok(value.unwrap_or_else(|| Scalar::null_ptr(cx))) + } + None => throw_ub_format!("loading from a non-existing TLS key: {}", key), + } + } + + pub fn store_tls( + &mut self, + key: TlsKey, + thread_id: ThreadId, + new_data: Scalar, + cx: &impl HasDataLayout, + ) -> InterpResult<'tcx> { + match self.keys.get_mut(&key) { + Some(TlsEntry { data, .. }) => { + if new_data.to_machine_usize(cx)? != 0 { + trace!("TLS key {} for thread {:?} stored: {:?}", key, thread_id, new_data); + data.insert(thread_id, new_data); + } else { + trace!("TLS key {} for thread {:?} removed", key, thread_id); + data.remove(&thread_id); + } + Ok(()) + } + None => throw_ub_format!("storing to a non-existing TLS key: {}", key), + } + } + + /// Set the thread wide destructor of the thread local storage for the given + /// thread. This function is used to implement `_tlv_atexit` shim on MacOS. + /// + /// Thread wide dtors are available only on MacOS. There is one destructor + /// per thread as can be guessed from the following comment in the + /// [`_tlv_atexit` + /// implementation](https://github.com/opensource-apple/dyld/blob/195030646877261f0c8c7ad8b001f52d6a26f514/src/threadLocalVariables.c#L389): + /// + /// NOTE: this does not need locks because it only operates on current thread data + pub fn set_macos_thread_dtor( + &mut self, + thread: ThreadId, + dtor: ty::Instance<'tcx>, + data: Scalar, + ) -> InterpResult<'tcx> { + if self.dtors_running.contains_key(&thread) { + // UB, according to libstd docs. + throw_ub_format!( + "setting thread's local storage destructor while destructors are already running" + ); + } + if self.macos_thread_dtors.insert(thread, (dtor, data)).is_some() { + throw_unsup_format!( + "setting more than one thread local storage destructor for the same thread is not supported" + ); + } + Ok(()) + } + + /// Returns a dtor, its argument and its index, if one is supposed to run. + /// `key` is the last dtors that was run; we return the *next* one after that. + /// + /// An optional destructor function may be associated with each key value. + /// At thread exit, if a key value has a non-NULL destructor pointer, + /// and the thread has a non-NULL value associated with that key, + /// the value of the key is set to NULL, and then the function pointed + /// to is called with the previously associated value as its sole argument. + /// **The order of destructor calls is unspecified if more than one destructor + /// exists for a thread when it exits.** + /// + /// If, after all the destructors have been called for all non-NULL values + /// with associated destructors, there are still some non-NULL values with + /// associated destructors, then the process is repeated. + /// If, after at least {PTHREAD_DESTRUCTOR_ITERATIONS} iterations of destructor + /// calls for outstanding non-NULL values, there are still some non-NULL values + /// with associated destructors, implementations may stop calling destructors, + /// or they may continue calling destructors until no non-NULL values with + /// associated destructors exist, even though this might result in an infinite loop. + fn fetch_tls_dtor( + &mut self, + key: Option, + thread_id: ThreadId, + ) -> Option<(ty::Instance<'tcx>, Scalar, TlsKey)> { + use std::ops::Bound::*; + + let thread_local = &mut self.keys; + let start = match key { + Some(key) => Excluded(key), + None => Unbounded, + }; + // We interpret the documentaion above (taken from POSIX) as saying that we need to iterate + // over all keys and run each destructor at least once before running any destructor a 2nd + // time. That's why we have `key` to indicate how far we got in the current iteration. If we + // return `None`, `schedule_next_pthread_tls_dtor` will re-try with `ket` set to `None` to + // start the next round. + // TODO: In the future, we might consider randomizing destructor order, but we still have to + // uphold this requirement. + for (&key, TlsEntry { data, dtor }) in thread_local.range_mut((start, Unbounded)) { + match data.entry(thread_id) { + BTreeEntry::Occupied(entry) => { + if let Some(dtor) = dtor { + // Set TLS data to NULL, and call dtor with old value. + let data_scalar = entry.remove(); + let ret = Some((*dtor, data_scalar, key)); + return ret; + } + } + BTreeEntry::Vacant(_) => {} + } + } + None + } + + /// Set that dtors are running for `thread`. It is guaranteed not to change + /// the existing values stored in `dtors_running` for this thread. Returns + /// `true` if dtors for `thread` are already running. + fn set_dtors_running_for_thread(&mut self, thread: ThreadId) -> bool { + match self.dtors_running.entry(thread) { + HashMapEntry::Occupied(_) => true, + HashMapEntry::Vacant(entry) => { + // We cannot just do `self.dtors_running.insert` because that + // would overwrite `last_dtor_key` with `None`. + entry.insert(RunningDtorsState { last_dtor_key: None }); + false + } + } + } + + /// Delete all TLS entries for the given thread. This function should be + /// called after all TLS destructors have already finished. + fn delete_all_thread_tls(&mut self, thread_id: ThreadId) { + for TlsEntry { data, .. } in self.keys.values_mut() { + data.remove(&thread_id); + } + } + + pub fn iter(&self, mut visitor: impl FnMut(&Scalar)) { + for scalar in self.keys.values().flat_map(|v| v.data.values()) { + visitor(scalar); + } + } +} + +impl<'mir, 'tcx: 'mir> EvalContextPrivExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + /// Schedule TLS destructors for Windows. + /// On windows, TLS destructors are managed by std. + fn schedule_windows_tls_dtors(&mut self) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + let active_thread = this.get_active_thread(); + + // Windows has a special magic linker section that is run on certain events. + // Instead of searching for that section and supporting arbitrary hooks in there + // (that would be basically https://github.com/rust-lang/miri/issues/450), + // we specifically look up the static in libstd that we know is placed + // in that section. + let thread_callback = + this.eval_windows("thread_local_key", "p_thread_callback")?.to_pointer(this)?; + let thread_callback = this.get_ptr_fn(thread_callback)?.as_instance()?; + + // FIXME: Technically, the reason should be `DLL_PROCESS_DETACH` when the main thread exits + // but std treats both the same. + let reason = this.eval_windows("c", "DLL_THREAD_DETACH")?; + + // The signature of this function is `unsafe extern "system" fn(h: c::LPVOID, dwReason: c::DWORD, pv: c::LPVOID)`. + // FIXME: `h` should be a handle to the current module and what `pv` should be is unknown + // but both are ignored by std + this.call_function( + thread_callback, + Abi::System { unwind: false }, + &[Scalar::null_ptr(this).into(), reason.into(), Scalar::null_ptr(this).into()], + None, + StackPopCleanup::Root { cleanup: true }, + )?; + + this.enable_thread(active_thread); + Ok(()) + } + + /// Schedule the MacOS thread destructor of the thread local storage to be + /// executed. Returns `true` if scheduled. + /// + /// Note: It is safe to call this function also on other Unixes. + fn schedule_macos_tls_dtor(&mut self) -> InterpResult<'tcx, bool> { + let this = self.eval_context_mut(); + let thread_id = this.get_active_thread(); + if let Some((instance, data)) = this.machine.tls.macos_thread_dtors.remove(&thread_id) { + trace!("Running macos dtor {:?} on {:?} at {:?}", instance, data, thread_id); + + this.call_function( + instance, + Abi::C { unwind: false }, + &[data.into()], + None, + StackPopCleanup::Root { cleanup: true }, + )?; + + // Enable the thread so that it steps through the destructor which + // we just scheduled. Since we deleted the destructor, it is + // guaranteed that we will schedule it again. The `dtors_running` + // flag will prevent the code from adding the destructor again. + this.enable_thread(thread_id); + Ok(true) + } else { + Ok(false) + } + } + + /// Schedule a pthread TLS destructor. Returns `true` if found + /// a destructor to schedule, and `false` otherwise. + fn schedule_next_pthread_tls_dtor(&mut self) -> InterpResult<'tcx, bool> { + let this = self.eval_context_mut(); + let active_thread = this.get_active_thread(); + + assert!(this.has_terminated(active_thread), "running TLS dtors for non-terminated thread"); + // Fetch next dtor after `key`. + let last_key = this.machine.tls.dtors_running[&active_thread].last_dtor_key; + let dtor = match this.machine.tls.fetch_tls_dtor(last_key, active_thread) { + dtor @ Some(_) => dtor, + // We ran each dtor once, start over from the beginning. + None => this.machine.tls.fetch_tls_dtor(None, active_thread), + }; + if let Some((instance, ptr, key)) = dtor { + this.machine.tls.dtors_running.get_mut(&active_thread).unwrap().last_dtor_key = + Some(key); + trace!("Running TLS dtor {:?} on {:?} at {:?}", instance, ptr, active_thread); + assert!( + !ptr.to_machine_usize(this).unwrap() != 0, + "data can't be NULL when dtor is called!" + ); + + this.call_function( + instance, + Abi::C { unwind: false }, + &[ptr.into()], + None, + StackPopCleanup::Root { cleanup: true }, + )?; + + this.enable_thread(active_thread); + return Ok(true); + } + this.machine.tls.dtors_running.get_mut(&active_thread).unwrap().last_dtor_key = None; + + Ok(false) + } +} + +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + /// Schedule an active thread's TLS destructor to run on the active thread. + /// Note that this function does not run the destructors itself, it just + /// schedules them one by one each time it is called and reenables the + /// thread so that it can be executed normally by the main execution loop. + /// + /// Note: we consistently run TLS destructors for all threads, including the + /// main thread. However, it is not clear that we should run the TLS + /// destructors for the main thread. See issue: + /// . + fn schedule_next_tls_dtor_for_active_thread(&mut self) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + let active_thread = this.get_active_thread(); + trace!("schedule_next_tls_dtor_for_active_thread on thread {:?}", active_thread); + + if !this.machine.tls.set_dtors_running_for_thread(active_thread) { + // This is the first time we got asked to schedule a destructor. The + // Windows schedule destructor function must be called exactly once, + // this is why it is in this block. + if this.tcx.sess.target.os == "windows" { + // On Windows, we signal that the thread quit by starting the + // relevant function, reenabling the thread, and going back to + // the scheduler. + this.schedule_windows_tls_dtors()?; + return Ok(()); + } + } + // The remaining dtors make some progress each time around the scheduler loop, + // until they return `false` to indicate that they are done. + + // The macOS thread wide destructor runs "before any TLS slots get + // freed", so do that first. + if this.schedule_macos_tls_dtor()? { + // We have scheduled a MacOS dtor to run on the thread. Execute it + // to completion and come back here. Scheduling a destructor + // destroys it, so we will not enter this branch again. + return Ok(()); + } + if this.schedule_next_pthread_tls_dtor()? { + // We have scheduled a pthread destructor and removed it from the + // destructors list. Run it to completion and come back here. + return Ok(()); + } + + // All dtors done! + this.machine.tls.delete_all_thread_tls(active_thread); + this.thread_terminated()?; + + Ok(()) + } +} diff --git a/src/tools/miri/src/shims/unix/android/dlsym.rs b/src/tools/miri/src/shims/unix/android/dlsym.rs new file mode 100644 index 0000000000000..4cb78d4dabdcf --- /dev/null +++ b/src/tools/miri/src/shims/unix/android/dlsym.rs @@ -0,0 +1,54 @@ +use rustc_middle::mir; + +use crate::helpers::check_arg_count; +use crate::*; + +#[derive(Debug, Copy, Clone)] +#[allow(non_camel_case_types)] +pub enum Dlsym { + signal, +} + +impl Dlsym { + // Returns an error for unsupported symbols, and None if this symbol + // should become a NULL pointer (pretend it does not exist). + pub fn from_str<'tcx>(name: &str) -> InterpResult<'tcx, Option> { + Ok(match name { + "signal" => Some(Dlsym::signal), + "android_set_abort_message" => None, + _ => throw_unsup_format!("unsupported Android dlsym: {}", name), + }) + } +} + +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + fn call_dlsym( + &mut self, + dlsym: Dlsym, + args: &[OpTy<'tcx, Provenance>], + dest: &PlaceTy<'tcx, Provenance>, + ret: Option, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + let ret = ret.expect("we don't support any diverging dlsym"); + assert!(this.tcx.sess.target.os == "android"); + + match dlsym { + Dlsym::signal => { + if !this.frame_in_std() { + throw_unsup_format!( + "`signal` support is crude and just enough for libstd to work" + ); + } + + let &[ref _sig, ref _func] = check_arg_count(args)?; + this.write_null(dest)?; + } + } + + log::trace!("{:?}", this.dump_place(**dest)); + this.go_to_block(ret); + Ok(()) + } +} diff --git a/src/tools/miri/src/shims/unix/android/foreign_items.rs b/src/tools/miri/src/shims/unix/android/foreign_items.rs new file mode 100644 index 0000000000000..756aed369f15b --- /dev/null +++ b/src/tools/miri/src/shims/unix/android/foreign_items.rs @@ -0,0 +1,26 @@ +use rustc_span::Symbol; +use rustc_target::spec::abi::Abi; + +use crate::*; +use shims::foreign_items::EmulateByNameResult; + +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_by_name( + &mut self, + link_name: Symbol, + _abi: Abi, + _args: &[OpTy<'tcx, Provenance>], + _dest: &PlaceTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, EmulateByNameResult<'mir, 'tcx>> { + let _this = self.eval_context_mut(); + #[allow(clippy::match_single_binding)] + match link_name.as_str() { + _ => return Ok(EmulateByNameResult::NotSupported), + } + + #[allow(unreachable_code)] + Ok(EmulateByNameResult::NeedsJumping) + } +} diff --git a/src/tools/miri/src/shims/unix/android/mod.rs b/src/tools/miri/src/shims/unix/android/mod.rs new file mode 100644 index 0000000000000..434f5f30b5a56 --- /dev/null +++ b/src/tools/miri/src/shims/unix/android/mod.rs @@ -0,0 +1,2 @@ +pub mod dlsym; +pub mod foreign_items; diff --git a/src/tools/miri/src/shims/unix/dlsym.rs b/src/tools/miri/src/shims/unix/dlsym.rs new file mode 100644 index 0000000000000..8bc19d18f2b29 --- /dev/null +++ b/src/tools/miri/src/shims/unix/dlsym.rs @@ -0,0 +1,55 @@ +use rustc_middle::mir; +use rustc_target::spec::abi::Abi; + +use crate::*; +use shims::unix::android::dlsym as android; +use shims::unix::freebsd::dlsym as freebsd; +use shims::unix::linux::dlsym as linux; +use shims::unix::macos::dlsym as macos; + +#[derive(Debug, Copy, Clone)] +pub enum Dlsym { + Android(android::Dlsym), + FreeBsd(freebsd::Dlsym), + Linux(linux::Dlsym), + MacOs(macos::Dlsym), +} + +impl Dlsym { + // Returns an error for unsupported symbols, and None if this symbol + // should become a NULL pointer (pretend it does not exist). + pub fn from_str<'tcx>(name: &str, target_os: &str) -> InterpResult<'tcx, Option> { + Ok(match target_os { + "android" => android::Dlsym::from_str(name)?.map(Dlsym::Android), + "freebsd" => freebsd::Dlsym::from_str(name)?.map(Dlsym::FreeBsd), + "linux" => linux::Dlsym::from_str(name)?.map(Dlsym::Linux), + "macos" => macos::Dlsym::from_str(name)?.map(Dlsym::MacOs), + _ => panic!("unsupported Unix OS {target_os}"), + }) + } +} + +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + fn call_dlsym( + &mut self, + dlsym: Dlsym, + abi: Abi, + args: &[OpTy<'tcx, Provenance>], + dest: &PlaceTy<'tcx, Provenance>, + ret: Option, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + + this.check_abi(abi, Abi::C { unwind: false })?; + + match dlsym { + Dlsym::Android(dlsym) => + android::EvalContextExt::call_dlsym(this, dlsym, args, dest, ret), + Dlsym::FreeBsd(dlsym) => + freebsd::EvalContextExt::call_dlsym(this, dlsym, args, dest, ret), + Dlsym::Linux(dlsym) => linux::EvalContextExt::call_dlsym(this, dlsym, args, dest, ret), + Dlsym::MacOs(dlsym) => macos::EvalContextExt::call_dlsym(this, dlsym, args, dest, ret), + } + } +} diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs new file mode 100644 index 0000000000000..153e5852dcc88 --- /dev/null +++ b/src/tools/miri/src/shims/unix/foreign_items.rs @@ -0,0 +1,606 @@ +use std::ffi::OsStr; + +use log::trace; + +use rustc_middle::ty::layout::LayoutOf; +use rustc_span::Symbol; +use rustc_target::abi::{Align, Size}; +use rustc_target::spec::abi::Abi; + +use crate::*; +use shims::foreign_items::EmulateByNameResult; +use shims::unix::fs::EvalContextExt as _; +use shims::unix::sync::EvalContextExt as _; +use shims::unix::thread::EvalContextExt as _; + +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_by_name( + &mut self, + link_name: Symbol, + abi: Abi, + args: &[OpTy<'tcx, Provenance>], + dest: &PlaceTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, EmulateByNameResult<'mir, 'tcx>> { + let this = self.eval_context_mut(); + + // See `fn emulate_foreign_item_by_name` in `shims/foreign_items.rs` for the general pattern. + #[rustfmt::skip] + match link_name.as_str() { + // Environment related shims + "getenv" => { + let [name] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.getenv(name)?; + this.write_pointer(result, dest)?; + } + "unsetenv" => { + let [name] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.unsetenv(name)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "setenv" => { + let [name, value, overwrite] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + this.read_scalar(overwrite)?.to_i32()?; + let result = this.setenv(name, value)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "getcwd" => { + let [buf, size] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.getcwd(buf, size)?; + this.write_pointer(result, dest)?; + } + "chdir" => { + let [path] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.chdir(path)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + + // File related shims + "open" | "open64" => { + // `open` is variadic, the third argument is only present when the second argument has O_CREAT (or on linux O_TMPFILE, but miri doesn't support that) set + this.check_abi_and_shim_symbol_clash(abi, Abi::C { unwind: false }, link_name)?; + let result = this.open(args)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "close" => { + let [fd] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.close(fd)?; + this.write_scalar(result, dest)?; + } + "fcntl" => { + // `fcntl` is variadic. The argument count is checked based on the first argument + // in `this.fcntl()`, so we do not use `check_shim` here. + this.check_abi_and_shim_symbol_clash(abi, Abi::C { unwind: false }, link_name)?; + let result = this.fcntl(args)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "read" => { + let [fd, buf, count] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let fd = this.read_scalar(fd)?.to_i32()?; + let buf = this.read_pointer(buf)?; + let count = this.read_scalar(count)?.to_machine_usize(this)?; + let result = this.read(fd, buf, count)?; + this.write_scalar(Scalar::from_machine_isize(result, this), dest)?; + } + "write" => { + let [fd, buf, n] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let fd = this.read_scalar(fd)?.to_i32()?; + let buf = this.read_pointer(buf)?; + let count = this.read_scalar(n)?.to_machine_usize(this)?; + trace!("Called write({:?}, {:?}, {:?})", fd, buf, count); + let result = this.write(fd, buf, count)?; + // Now, `result` is the value we return back to the program. + this.write_scalar(Scalar::from_machine_isize(result, this), dest)?; + } + "unlink" => { + let [path] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.unlink(path)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "symlink" => { + let [target, linkpath] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.symlink(target, linkpath)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "rename" => { + let [oldpath, newpath] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.rename(oldpath, newpath)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "mkdir" => { + let [path, mode] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.mkdir(path, mode)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "rmdir" => { + let [path] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.rmdir(path)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "opendir" => { + let [name] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.opendir(name)?; + this.write_scalar(result, dest)?; + } + "closedir" => { + let [dirp] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.closedir(dirp)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "lseek64" => { + let [fd, offset, whence] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.lseek64(fd, offset, whence)?; + this.write_scalar(result, dest)?; + } + "ftruncate64" => { + let [fd, length] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.ftruncate64(fd, length)?; + this.write_scalar(result, dest)?; + } + "fsync" => { + let [fd] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.fsync(fd)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "fdatasync" => { + let [fd] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.fdatasync(fd)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "readlink" => { + let [pathname, buf, bufsize] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.readlink(pathname, buf, bufsize)?; + this.write_scalar(Scalar::from_machine_isize(result, this), dest)?; + } + "posix_fadvise" => { + let [fd, offset, len, advice] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + this.read_scalar(fd)?.to_i32()?; + this.read_scalar(offset)?.to_machine_isize(this)?; + this.read_scalar(len)?.to_machine_isize(this)?; + this.read_scalar(advice)?.to_i32()?; + // fadvise is only informational, we can ignore it. + this.write_null(dest)?; + } + "realpath" => { + let [path, resolved_path] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.realpath(path, resolved_path)?; + this.write_scalar(result, dest)?; + } + "mkstemp" => { + let [template] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.mkstemp(template)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + + // Time related shims + "gettimeofday" => { + let [tv, tz] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.gettimeofday(tv, tz)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + + // Allocation + "posix_memalign" => { + let [ret, align, size] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let ret = this.deref_operand(ret)?; + let align = this.read_scalar(align)?.to_machine_usize(this)?; + let size = this.read_scalar(size)?.to_machine_usize(this)?; + // Align must be power of 2, and also at least ptr-sized (POSIX rules). + // But failure to adhere to this is not UB, it's an error condition. + if !align.is_power_of_two() || align < this.pointer_size().bytes() { + let einval = this.eval_libc_i32("EINVAL")?; + this.write_int(einval, dest)?; + } else { + if size == 0 { + this.write_null(&ret.into())?; + } else { + let ptr = this.allocate_ptr( + Size::from_bytes(size), + Align::from_bytes(align).unwrap(), + MiriMemoryKind::C.into(), + )?; + this.write_pointer(ptr, &ret.into())?; + } + this.write_null(dest)?; + } + } + + // Dynamic symbol loading + "dlsym" => { + let [handle, symbol] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + this.read_scalar(handle)?.to_machine_usize(this)?; + let symbol = this.read_pointer(symbol)?; + let symbol_name = this.read_c_str(symbol)?; + if let Some(dlsym) = Dlsym::from_str(symbol_name, &this.tcx.sess.target.os)? { + let ptr = this.create_fn_alloc_ptr(FnVal::Other(dlsym)); + this.write_pointer(ptr, dest)?; + } else { + this.write_null(dest)?; + } + } + + // Querying system information + "sysconf" => { + let [name] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let name = this.read_scalar(name)?.to_i32()?; + + // FIXME: Which of these are POSIX, and which are GNU/Linux? + // At least the names seem to all also exist on macOS. + let sysconfs: &[(&str, fn(&MiriInterpCx<'_, '_>) -> Scalar)] = &[ + ("_SC_PAGESIZE", |this| Scalar::from_int(PAGE_SIZE, this.pointer_size())), + ("_SC_NPROCESSORS_CONF", |this| Scalar::from_int(NUM_CPUS, this.pointer_size())), + ("_SC_NPROCESSORS_ONLN", |this| Scalar::from_int(NUM_CPUS, this.pointer_size())), + // 512 seems to be a reasonable default. The value is not critical, in + // the sense that getpwuid_r takes and checks the buffer length. + ("_SC_GETPW_R_SIZE_MAX", |this| Scalar::from_int(512, this.pointer_size())) + ]; + let mut result = None; + for &(sysconf_name, value) in sysconfs { + let sysconf_name = this.eval_libc_i32(sysconf_name)?; + if sysconf_name == name { + result = Some(value(this)); + break; + } + } + if let Some(result) = result { + this.write_scalar(result, dest)?; + } else { + throw_unsup_format!("unimplemented sysconf name: {}", name) + } + } + + // Thread-local storage + "pthread_key_create" => { + let [key, dtor] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let key_place = this.deref_operand(key)?; + let dtor = this.read_pointer(dtor)?; + + // Extract the function type out of the signature (that seems easier than constructing it ourselves). + let dtor = if !this.ptr_is_null(dtor)? { + Some(this.get_ptr_fn(dtor)?.as_instance()?) + } else { + None + }; + + // Figure out how large a pthread TLS key actually is. + // To this end, deref the argument type. This is `libc::pthread_key_t`. + let key_type = key.layout.ty + .builtin_deref(true) + .ok_or_else(|| err_ub_format!( + "wrong signature used for `pthread_key_create`: first argument must be a raw pointer." + ))? + .ty; + let key_layout = this.layout_of(key_type)?; + + // Create key and write it into the memory where `key_ptr` wants it. + let key = this.machine.tls.create_tls_key(dtor, key_layout.size)?; + this.write_scalar(Scalar::from_uint(key, key_layout.size), &key_place.into())?; + + // Return success (`0`). + this.write_null(dest)?; + } + "pthread_key_delete" => { + let [key] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let key = this.read_scalar(key)?.to_bits(key.layout.size)?; + this.machine.tls.delete_tls_key(key)?; + // Return success (0) + this.write_null(dest)?; + } + "pthread_getspecific" => { + let [key] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let key = this.read_scalar(key)?.to_bits(key.layout.size)?; + let active_thread = this.get_active_thread(); + let ptr = this.machine.tls.load_tls(key, active_thread, this)?; + this.write_scalar(ptr, dest)?; + } + "pthread_setspecific" => { + let [key, new_ptr] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let key = this.read_scalar(key)?.to_bits(key.layout.size)?; + let active_thread = this.get_active_thread(); + let new_data = this.read_scalar(new_ptr)?; + this.machine.tls.store_tls(key, active_thread, new_data, &*this.tcx)?; + + // Return success (`0`). + this.write_null(dest)?; + } + + // Synchronization primitives + "pthread_mutexattr_init" => { + let [attr] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.pthread_mutexattr_init(attr)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "pthread_mutexattr_settype" => { + let [attr, kind] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.pthread_mutexattr_settype(attr, kind)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "pthread_mutexattr_destroy" => { + let [attr] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.pthread_mutexattr_destroy(attr)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "pthread_mutex_init" => { + let [mutex, attr] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.pthread_mutex_init(mutex, attr)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "pthread_mutex_lock" => { + let [mutex] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.pthread_mutex_lock(mutex)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "pthread_mutex_trylock" => { + let [mutex] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.pthread_mutex_trylock(mutex)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "pthread_mutex_unlock" => { + let [mutex] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.pthread_mutex_unlock(mutex)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "pthread_mutex_destroy" => { + let [mutex] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.pthread_mutex_destroy(mutex)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "pthread_rwlock_rdlock" => { + let [rwlock] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.pthread_rwlock_rdlock(rwlock)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "pthread_rwlock_tryrdlock" => { + let [rwlock] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.pthread_rwlock_tryrdlock(rwlock)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "pthread_rwlock_wrlock" => { + let [rwlock] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.pthread_rwlock_wrlock(rwlock)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "pthread_rwlock_trywrlock" => { + let [rwlock] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.pthread_rwlock_trywrlock(rwlock)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "pthread_rwlock_unlock" => { + let [rwlock] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.pthread_rwlock_unlock(rwlock)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "pthread_rwlock_destroy" => { + let [rwlock] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.pthread_rwlock_destroy(rwlock)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "pthread_condattr_init" => { + let [attr] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.pthread_condattr_init(attr)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "pthread_condattr_destroy" => { + let [attr] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.pthread_condattr_destroy(attr)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "pthread_cond_init" => { + let [cond, attr] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.pthread_cond_init(cond, attr)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "pthread_cond_signal" => { + let [cond] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.pthread_cond_signal(cond)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "pthread_cond_broadcast" => { + let [cond] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.pthread_cond_broadcast(cond)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "pthread_cond_wait" => { + let [cond, mutex] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.pthread_cond_wait(cond, mutex)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "pthread_cond_timedwait" => { + let [cond, mutex, abstime] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + this.pthread_cond_timedwait(cond, mutex, abstime, dest)?; + } + "pthread_cond_destroy" => { + let [cond] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.pthread_cond_destroy(cond)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + + // Threading + "pthread_create" => { + let [thread, attr, start, arg] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.pthread_create(thread, attr, start, arg)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "pthread_join" => { + let [thread, retval] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.pthread_join(thread, retval)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "pthread_detach" => { + let [thread] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.pthread_detach(thread)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "pthread_self" => { + let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let res = this.pthread_self()?; + this.write_scalar(res, dest)?; + } + "sched_yield" => { + let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.sched_yield()?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "nanosleep" => { + let [req, rem] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.nanosleep(req, rem)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + + // Miscellaneous + "isatty" => { + let [fd] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.isatty(fd)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "pthread_atfork" => { + let [prepare, parent, child] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + this.read_pointer(prepare)?; + this.read_pointer(parent)?; + this.read_pointer(child)?; + // We do not support forking, so there is nothing to do here. + this.write_null(dest)?; + } + "strerror_r" | "__xpg_strerror_r" => { + let [errnum, buf, buflen] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let errnum = this.read_scalar(errnum)?; + let buf = this.read_pointer(buf)?; + let buflen = this.read_scalar(buflen)?.to_machine_usize(this)?; + + let error = this.try_errnum_to_io_error(errnum)?; + let formatted = match error { + Some(err) => format!("{err}"), + None => format!(""), + }; + let (complete, _) = this.write_os_str_to_c_str(OsStr::new(&formatted), buf, buflen)?; + let ret = if complete { 0 } else { this.eval_libc_i32("ERANGE")? }; + this.write_int(ret, dest)?; + } + "getpid" => { + let [] = this.check_shim(abi, Abi::C { unwind: false}, link_name, args)?; + let result = this.getpid()?; + this.write_scalar(Scalar::from_i32(result), 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. + "pthread_attr_getguardsize" + if this.frame_in_std() => { + let [_attr, guard_size] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let guard_size = this.deref_operand(guard_size)?; + let guard_size_layout = this.libc_ty_layout("size_t")?; + this.write_scalar(Scalar::from_uint(crate::PAGE_SIZE, guard_size_layout.size), &guard_size.into())?; + + // Return success (`0`). + this.write_null(dest)?; + } + + | "pthread_attr_init" + | "pthread_attr_destroy" + if this.frame_in_std() => { + let [_] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + this.write_null(dest)?; + } + | "pthread_attr_setstacksize" + if this.frame_in_std() => { + let [_, _] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + this.write_null(dest)?; + } + + "pthread_attr_getstack" + if this.frame_in_std() => { + // We don't support "pthread_attr_setstack", so we just pretend all stacks have the same values here. + // Hence we can mostly ignore the input `attr_place`. + let [attr_place, addr_place, size_place] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let _attr_place = this.deref_operand(attr_place)?; + let addr_place = this.deref_operand(addr_place)?; + let size_place = this.deref_operand(size_place)?; + + this.write_scalar( + Scalar::from_uint(STACK_ADDR, this.pointer_size()), + &addr_place.into(), + )?; + this.write_scalar( + Scalar::from_uint(STACK_SIZE, this.pointer_size()), + &size_place.into(), + )?; + + // Return success (`0`). + this.write_null(dest)?; + } + + | "signal" + | "sigaltstack" + if this.frame_in_std() => { + let [_, _] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + this.write_null(dest)?; + } + | "sigaction" + | "mprotect" + if this.frame_in_std() => { + let [_, _, _] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + this.write_null(dest)?; + } + + "getuid" + if this.frame_in_std() => { + let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + // FOr now, just pretend we always have this fixed UID. + this.write_int(super::UID, dest)?; + } + + "getpwuid_r" if this.frame_in_std() => { + let [uid, pwd, buf, buflen, result] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + this.check_no_isolation("`getpwuid_r`")?; + + let uid = this.read_scalar(uid)?.to_u32()?; + let pwd = this.deref_operand(pwd)?; + let buf = this.read_pointer(buf)?; + let buflen = this.read_scalar(buflen)?.to_machine_usize(this)?; + let result = this.deref_operand(result)?; + + // Must be for "us". + if uid != crate::shims::unix::UID { + throw_unsup_format!("`getpwuid_r` on other users is not supported"); + } + + // Reset all fields to `uninit` to make sure nobody reads them. + // (This is a std-only shim so we are okay with such hacks.) + this.write_uninit(&pwd.into())?; + + // We only set the home_dir field. + #[allow(deprecated)] + let home_dir = std::env::home_dir().unwrap(); + let (written, _) = this.write_path_to_c_str(&home_dir, buf, buflen)?; + let pw_dir = this.mplace_field_named(&pwd, "pw_dir")?; + this.write_pointer(buf, &pw_dir.into())?; + + if written { + this.write_pointer(pwd.ptr, &result.into())?; + this.write_null(dest)?; + } else { + this.write_null(&result.into())?; + this.write_scalar(this.eval_libc("ERANGE")?, dest)?; + } + } + + // Platform-specific shims + _ => { + let target_os = &*this.tcx.sess.target.os; + match target_os { + "android" => return shims::unix::android::foreign_items::EvalContextExt::emulate_foreign_item_by_name(this, link_name, abi, args, dest), + "freebsd" => return shims::unix::freebsd::foreign_items::EvalContextExt::emulate_foreign_item_by_name(this, link_name, abi, args, dest), + "linux" => return shims::unix::linux::foreign_items::EvalContextExt::emulate_foreign_item_by_name(this, link_name, abi, args, dest), + "macos" => return shims::unix::macos::foreign_items::EvalContextExt::emulate_foreign_item_by_name(this, link_name, abi, args, dest), + _ => panic!("unsupported Unix OS {target_os}"), + } + } + }; + + Ok(EmulateByNameResult::NeedsJumping) + } +} diff --git a/src/tools/miri/src/shims/unix/freebsd/dlsym.rs b/src/tools/miri/src/shims/unix/freebsd/dlsym.rs new file mode 100644 index 0000000000000..d759ffb8994b7 --- /dev/null +++ b/src/tools/miri/src/shims/unix/freebsd/dlsym.rs @@ -0,0 +1,36 @@ +use rustc_middle::mir; + +use crate::*; + +#[derive(Debug, Copy, Clone)] +#[allow(non_camel_case_types)] +pub enum Dlsym {} + +impl Dlsym { + // Returns an error for unsupported symbols, and None if this symbol + // should become a NULL pointer (pretend it does not exist). + pub fn from_str<'tcx>(name: &str) -> InterpResult<'tcx, Option> { + throw_unsup_format!("unsupported FreeBSD dlsym: {}", name) + } +} + +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + fn call_dlsym( + &mut self, + dlsym: Dlsym, + _args: &[OpTy<'tcx, Provenance>], + _dest: &PlaceTy<'tcx, Provenance>, + ret: Option, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + let _ret = ret.expect("we don't support any diverging dlsym"); + assert!(this.tcx.sess.target.os == "freebsd"); + + match dlsym {} + + //trace!("{:?}", this.dump_place(**dest)); + //this.go_to_block(ret); + //Ok(()) + } +} diff --git a/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs b/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs new file mode 100644 index 0000000000000..70798f9817453 --- /dev/null +++ b/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs @@ -0,0 +1,45 @@ +use rustc_span::Symbol; +use rustc_target::spec::abi::Abi; + +use crate::*; +use shims::foreign_items::EmulateByNameResult; +use shims::unix::thread::EvalContextExt as _; + +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_by_name( + &mut self, + link_name: Symbol, + abi: Abi, + args: &[OpTy<'tcx, Provenance>], + dest: &PlaceTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, EmulateByNameResult<'mir, 'tcx>> { + let this = self.eval_context_mut(); + match link_name.as_str() { + // Threading + "pthread_attr_get_np" if this.frame_in_std() => { + let [_thread, _attr] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + this.write_null(dest)?; + } + "pthread_set_name_np" => { + let [thread, name] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let res = + this.pthread_setname_np(this.read_scalar(thread)?, this.read_scalar(name)?)?; + this.write_scalar(res, dest)?; + } + + // errno + "__error" => { + let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let errno_place = this.last_error_place()?; + this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?; + } + + _ => return Ok(EmulateByNameResult::NotSupported), + } + Ok(EmulateByNameResult::NeedsJumping) + } +} diff --git a/src/tools/miri/src/shims/unix/freebsd/mod.rs b/src/tools/miri/src/shims/unix/freebsd/mod.rs new file mode 100644 index 0000000000000..434f5f30b5a56 --- /dev/null +++ b/src/tools/miri/src/shims/unix/freebsd/mod.rs @@ -0,0 +1,2 @@ +pub mod dlsym; +pub mod foreign_items; diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs new file mode 100644 index 0000000000000..8464c4589ed5d --- /dev/null +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -0,0 +1,1952 @@ +use std::borrow::Cow; +use std::collections::BTreeMap; +use std::convert::TryInto; +use std::fs::{ + read_dir, remove_dir, remove_file, rename, DirBuilder, File, FileType, OpenOptions, ReadDir, +}; +use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write}; +use std::path::{Path, PathBuf}; +use std::time::SystemTime; + +use log::trace; + +use rustc_data_structures::fx::FxHashMap; +use rustc_middle::ty::{self, layout::LayoutOf}; +use rustc_target::abi::{Align, Size}; + +use crate::shims::os_str::bytes_to_os_str; +use crate::*; +use shims::os_str::os_str_to_bytes; +use shims::time::system_time_to_duration; + +#[derive(Debug)] +struct FileHandle { + file: File, + writable: bool, +} + +trait FileDescriptor: std::fmt::Debug { + fn name(&self) -> &'static str; + + fn as_file_handle<'tcx>(&self) -> InterpResult<'tcx, &FileHandle> { + throw_unsup_format!("{} cannot be used as FileHandle", self.name()); + } + + fn read<'tcx>( + &mut self, + _communicate_allowed: bool, + _bytes: &mut [u8], + ) -> InterpResult<'tcx, io::Result> { + throw_unsup_format!("cannot read from {}", self.name()); + } + + fn write<'tcx>( + &self, + _communicate_allowed: bool, + _bytes: &[u8], + ) -> InterpResult<'tcx, io::Result> { + throw_unsup_format!("cannot write to {}", self.name()); + } + + fn seek<'tcx>( + &mut self, + _communicate_allowed: bool, + _offset: SeekFrom, + ) -> InterpResult<'tcx, io::Result> { + throw_unsup_format!("cannot seek on {}", self.name()); + } + + fn close<'tcx>( + self: Box, + _communicate_allowed: bool, + ) -> InterpResult<'tcx, io::Result> { + throw_unsup_format!("cannot close {}", self.name()); + } + + fn dup(&mut self) -> io::Result>; + + #[cfg(unix)] + fn as_unix_host_fd(&self) -> Option { + None + } +} + +impl FileDescriptor for FileHandle { + fn name(&self) -> &'static str { + "FILE" + } + + fn as_file_handle<'tcx>(&self) -> InterpResult<'tcx, &FileHandle> { + Ok(self) + } + + fn read<'tcx>( + &mut self, + communicate_allowed: bool, + bytes: &mut [u8], + ) -> InterpResult<'tcx, io::Result> { + assert!(communicate_allowed, "isolation should have prevented even opening a file"); + Ok(self.file.read(bytes)) + } + + fn write<'tcx>( + &self, + communicate_allowed: bool, + bytes: &[u8], + ) -> InterpResult<'tcx, io::Result> { + assert!(communicate_allowed, "isolation should have prevented even opening a file"); + Ok((&mut &self.file).write(bytes)) + } + + fn seek<'tcx>( + &mut self, + communicate_allowed: bool, + offset: SeekFrom, + ) -> InterpResult<'tcx, io::Result> { + assert!(communicate_allowed, "isolation should have prevented even opening a file"); + Ok(self.file.seek(offset)) + } + + fn close<'tcx>( + self: Box, + communicate_allowed: bool, + ) -> InterpResult<'tcx, io::Result> { + assert!(communicate_allowed, "isolation should have prevented even opening a file"); + // We sync the file if it was opened in a mode different than read-only. + if self.writable { + // `File::sync_all` does the checks that are done when closing a file. We do this to + // to handle possible errors correctly. + let result = self.file.sync_all().map(|_| 0i32); + // Now we actually close the file. + drop(self); + // And return the result. + Ok(result) + } else { + // We drop the file, this closes it but ignores any errors + // produced when closing it. This is done because + // `File::sync_all` cannot be done over files like + // `/dev/urandom` which are read-only. Check + // https://github.com/rust-lang/miri/issues/999#issuecomment-568920439 + // for a deeper discussion. + drop(self); + Ok(Ok(0)) + } + } + + fn dup(&mut self) -> io::Result> { + let duplicated = self.file.try_clone()?; + Ok(Box::new(FileHandle { file: duplicated, writable: self.writable })) + } + + #[cfg(unix)] + fn as_unix_host_fd(&self) -> Option { + use std::os::unix::io::AsRawFd; + Some(self.file.as_raw_fd()) + } +} + +impl FileDescriptor for io::Stdin { + fn name(&self) -> &'static str { + "stdin" + } + + fn read<'tcx>( + &mut self, + communicate_allowed: bool, + bytes: &mut [u8], + ) -> InterpResult<'tcx, io::Result> { + if !communicate_allowed { + // We want isolation mode to be deterministic, so we have to disallow all reads, even stdin. + helpers::isolation_abort_error("`read` from stdin")?; + } + Ok(Read::read(self, bytes)) + } + + fn dup(&mut self) -> io::Result> { + Ok(Box::new(io::stdin())) + } + + #[cfg(unix)] + fn as_unix_host_fd(&self) -> Option { + Some(libc::STDIN_FILENO) + } +} + +impl FileDescriptor for io::Stdout { + fn name(&self) -> &'static str { + "stdout" + } + + fn write<'tcx>( + &self, + _communicate_allowed: bool, + bytes: &[u8], + ) -> InterpResult<'tcx, io::Result> { + // We allow writing to stderr even with isolation enabled. + let result = Write::write(&mut { self }, bytes); + // Stdout is buffered, flush to make sure it appears on the + // screen. This is the write() syscall of the interpreted + // program, we want it to correspond to a write() syscall on + // the host -- there is no good in adding extra buffering + // here. + io::stdout().flush().unwrap(); + + Ok(result) + } + + fn dup(&mut self) -> io::Result> { + Ok(Box::new(io::stdout())) + } + + #[cfg(unix)] + fn as_unix_host_fd(&self) -> Option { + Some(libc::STDOUT_FILENO) + } +} + +impl FileDescriptor for io::Stderr { + fn name(&self) -> &'static str { + "stderr" + } + + fn write<'tcx>( + &self, + _communicate_allowed: bool, + bytes: &[u8], + ) -> InterpResult<'tcx, io::Result> { + // We allow writing to stderr even with isolation enabled. + // No need to flush, stderr is not buffered. + Ok(Write::write(&mut { self }, bytes)) + } + + fn dup(&mut self) -> io::Result> { + Ok(Box::new(io::stderr())) + } + + #[cfg(unix)] + fn as_unix_host_fd(&self) -> Option { + Some(libc::STDERR_FILENO) + } +} + +#[derive(Debug)] +struct DummyOutput; + +impl FileDescriptor for DummyOutput { + fn name(&self) -> &'static str { + "stderr and stdout" + } + + fn write<'tcx>( + &self, + _communicate_allowed: bool, + bytes: &[u8], + ) -> InterpResult<'tcx, io::Result> { + // We just don't write anything, but report to the user that we did. + Ok(Ok(bytes.len())) + } + + fn dup(&mut self) -> io::Result> { + Ok(Box::new(DummyOutput)) + } +} + +#[derive(Debug)] +pub struct FileHandler { + handles: BTreeMap>, +} + +impl FileHandler { + pub(crate) fn new(mute_stdout_stderr: bool) -> FileHandler { + let mut handles: BTreeMap<_, Box> = BTreeMap::new(); + handles.insert(0i32, Box::new(io::stdin())); + if mute_stdout_stderr { + handles.insert(1i32, Box::new(DummyOutput)); + handles.insert(2i32, Box::new(DummyOutput)); + } else { + handles.insert(1i32, Box::new(io::stdout())); + handles.insert(2i32, Box::new(io::stderr())); + } + FileHandler { handles } + } + + fn insert_fd(&mut self, file_handle: Box) -> i32 { + self.insert_fd_with_min_fd(file_handle, 0) + } + + fn insert_fd_with_min_fd(&mut self, file_handle: Box, min_fd: i32) -> i32 { + // Find the lowest unused FD, starting from min_fd. If the first such unused FD is in + // between used FDs, the find_map combinator will return it. If the first such unused FD + // is after all other used FDs, the find_map combinator will return None, and we will use + // the FD following the greatest FD thus far. + let candidate_new_fd = + self.handles.range(min_fd..).zip(min_fd..).find_map(|((fd, _fh), counter)| { + if *fd != counter { + // There was a gap in the fds stored, return the first unused one + // (note that this relies on BTreeMap iterating in key order) + Some(counter) + } else { + // This fd is used, keep going + None + } + }); + let new_fd = candidate_new_fd.unwrap_or_else(|| { + // find_map ran out of BTreeMap entries before finding a free fd, use one plus the + // maximum fd in the map + self.handles + .last_key_value() + .map(|(fd, _)| fd.checked_add(1).unwrap()) + .unwrap_or(min_fd) + }); + + self.handles.try_insert(new_fd, file_handle).unwrap(); + new_fd + } +} + +impl<'mir, 'tcx: 'mir> EvalContextExtPrivate<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +trait EvalContextExtPrivate<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + fn macos_stat_write_buf( + &mut self, + metadata: FileMetadata, + buf_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + let mode: u16 = metadata.mode.to_u16()?; + + let (access_sec, access_nsec) = metadata.accessed.unwrap_or((0, 0)); + let (created_sec, created_nsec) = metadata.created.unwrap_or((0, 0)); + let (modified_sec, modified_nsec) = metadata.modified.unwrap_or((0, 0)); + + let buf = this.deref_operand(buf_op)?; + this.write_int_fields_named( + &[ + ("st_dev", 0), + ("st_mode", mode.into()), + ("st_nlink", 0), + ("st_ino", 0), + ("st_uid", 0), + ("st_gid", 0), + ("st_rdev", 0), + ("st_atime", access_sec.into()), + ("st_atime_nsec", access_nsec.into()), + ("st_mtime", modified_sec.into()), + ("st_mtime_nsec", modified_nsec.into()), + ("st_ctime", 0), + ("st_ctime_nsec", 0), + ("st_birthtime", created_sec.into()), + ("st_birthtime_nsec", created_nsec.into()), + ("st_size", metadata.size.into()), + ("st_blocks", 0), + ("st_blksize", 0), + ("st_flags", 0), + ("st_gen", 0), + ], + &buf, + )?; + + Ok(0) + } + + /// Function used when a handle is not found inside `FileHandler`. It returns `Ok(-1)`and sets + /// the last OS error to `libc::EBADF` (invalid file descriptor). This function uses + /// `T: From` instead of `i32` directly because some fs functions return different integer + /// types (like `read`, that returns an `i64`). + fn handle_not_found>(&mut self) -> InterpResult<'tcx, T> { + let this = self.eval_context_mut(); + let ebadf = this.eval_libc("EBADF")?; + this.set_last_error(ebadf)?; + Ok((-1).into()) + } + + fn file_type_to_d_type( + &mut self, + file_type: std::io::Result, + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + match file_type { + Ok(file_type) => { + if file_type.is_dir() { + Ok(this.eval_libc("DT_DIR")?.to_u8()?.into()) + } else if file_type.is_file() { + Ok(this.eval_libc("DT_REG")?.to_u8()?.into()) + } else if file_type.is_symlink() { + Ok(this.eval_libc("DT_LNK")?.to_u8()?.into()) + } else { + // Certain file types are only supported when the host is a Unix system. + // (i.e. devices and sockets) If it is, check those cases, if not, fall back to + // DT_UNKNOWN sooner. + + #[cfg(unix)] + { + use std::os::unix::fs::FileTypeExt; + if file_type.is_block_device() { + Ok(this.eval_libc("DT_BLK")?.to_u8()?.into()) + } else if file_type.is_char_device() { + Ok(this.eval_libc("DT_CHR")?.to_u8()?.into()) + } else if file_type.is_fifo() { + Ok(this.eval_libc("DT_FIFO")?.to_u8()?.into()) + } else if file_type.is_socket() { + Ok(this.eval_libc("DT_SOCK")?.to_u8()?.into()) + } else { + Ok(this.eval_libc("DT_UNKNOWN")?.to_u8()?.into()) + } + } + #[cfg(not(unix))] + Ok(this.eval_libc("DT_UNKNOWN")?.to_u8()?.into()) + } + } + Err(e) => + match e.raw_os_error() { + Some(error) => Ok(error), + None => + throw_unsup_format!( + "the error {} couldn't be converted to a return value", + e + ), + }, + } + } +} + +/// An open directory, tracked by DirHandler. +#[derive(Debug)] +pub struct OpenDir { + /// The directory reader on the host. + read_dir: ReadDir, + /// The most recent entry returned by readdir() + entry: Pointer>, +} + +impl OpenDir { + fn new(read_dir: ReadDir) -> Self { + // We rely on `free` being a NOP on null pointers. + Self { read_dir, entry: Pointer::null() } + } +} + +#[derive(Debug)] +pub struct DirHandler { + /// Directory iterators used to emulate libc "directory streams", as used in opendir, readdir, + /// and closedir. + /// + /// When opendir is called, a directory iterator is created on the host for the target + /// directory, and an entry is stored in this hash map, indexed by an ID which represents + /// the directory stream. When readdir is called, the directory stream ID is used to look up + /// the corresponding ReadDir iterator from this map, and information from the next + /// directory entry is returned. When closedir is called, the ReadDir iterator is removed from + /// the map. + streams: FxHashMap, + /// ID number to be used by the next call to opendir + next_id: u64, +} + +impl DirHandler { + #[allow(clippy::integer_arithmetic)] + fn insert_new(&mut self, read_dir: ReadDir) -> u64 { + let id = self.next_id; + self.next_id += 1; + self.streams.try_insert(id, OpenDir::new(read_dir)).unwrap(); + id + } +} + +impl Default for DirHandler { + fn default() -> DirHandler { + DirHandler { + streams: FxHashMap::default(), + // Skip 0 as an ID, because it looks like a null pointer to libc + next_id: 1, + } + } +} + +fn maybe_sync_file( + file: &File, + writable: bool, + operation: fn(&File) -> std::io::Result<()>, +) -> std::io::Result { + if !writable && cfg!(windows) { + // sync_all() and sync_data() will return an error on Windows hosts if the file is not opened + // for writing. (FlushFileBuffers requires that the file handle have the + // GENERIC_WRITE right) + Ok(0i32) + } else { + let result = operation(file); + result.map(|_| 0i32) + } +} + +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + fn open(&mut self, args: &[OpTy<'tcx, Provenance>]) -> InterpResult<'tcx, i32> { + if args.len() < 2 { + throw_ub_format!( + "incorrect number of arguments for `open`: got {}, expected at least 2", + args.len() + ); + } + + let this = self.eval_context_mut(); + + let path = this.read_pointer(&args[0])?; + let flag = this.read_scalar(&args[1])?.to_i32()?; + + let mut options = OpenOptions::new(); + + let o_rdonly = this.eval_libc_i32("O_RDONLY")?; + let o_wronly = this.eval_libc_i32("O_WRONLY")?; + let o_rdwr = this.eval_libc_i32("O_RDWR")?; + // The first two bits of the flag correspond to the access mode in linux, macOS and + // windows. We need to check that in fact the access mode flags for the current target + // only use these two bits, otherwise we are in an unsupported target and should error. + if (o_rdonly | o_wronly | o_rdwr) & !0b11 != 0 { + throw_unsup_format!("access mode flags on this target are unsupported"); + } + let mut writable = true; + + // Now we check the access mode + let access_mode = flag & 0b11; + + if access_mode == o_rdonly { + writable = false; + options.read(true); + } else if access_mode == o_wronly { + options.write(true); + } else if access_mode == o_rdwr { + options.read(true).write(true); + } else { + throw_unsup_format!("unsupported access mode {:#x}", access_mode); + } + // We need to check that there aren't unsupported options in `flag`. For this we try to + // reproduce the content of `flag` in the `mirror` variable using only the supported + // options. + let mut mirror = access_mode; + + let o_append = this.eval_libc_i32("O_APPEND")?; + if flag & o_append != 0 { + options.append(true); + mirror |= o_append; + } + let o_trunc = this.eval_libc_i32("O_TRUNC")?; + if flag & o_trunc != 0 { + options.truncate(true); + mirror |= o_trunc; + } + let o_creat = this.eval_libc_i32("O_CREAT")?; + if flag & o_creat != 0 { + // Get the mode. On macOS, the argument type `mode_t` is actually `u16`, but + // C integer promotion rules mean that on the ABI level, it gets passed as `u32` + // (see https://github.com/rust-lang/rust/issues/71915). + let mode = if let Some(arg) = args.get(2) { + this.read_scalar(arg)?.to_u32()? + } else { + throw_ub_format!( + "incorrect number of arguments for `open` with `O_CREAT`: got {}, expected at least 3", + args.len() + ); + }; + + if mode != 0o666 { + throw_unsup_format!("non-default mode 0o{:o} is not supported", mode); + } + + mirror |= o_creat; + + let o_excl = this.eval_libc_i32("O_EXCL")?; + if flag & o_excl != 0 { + mirror |= o_excl; + options.create_new(true); + } else { + options.create(true); + } + } + let o_cloexec = this.eval_libc_i32("O_CLOEXEC")?; + if flag & o_cloexec != 0 { + // We do not need to do anything for this flag because `std` already sets it. + // (Technically we do not support *not* setting this flag, but we ignore that.) + mirror |= o_cloexec; + } + // If `flag` is not equal to `mirror`, there is an unsupported option enabled in `flag`, + // then we throw an error. + if flag != mirror { + throw_unsup_format!("unsupported flags {:#x}", flag & !mirror); + } + + let path = this.read_path_from_c_str(path)?; + + // Reject if isolation is enabled. + if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { + this.reject_in_isolation("`open`", reject_with)?; + this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?; + return Ok(-1); + } + + let fd = options.open(&path).map(|file| { + let fh = &mut this.machine.file_handler; + fh.insert_fd(Box::new(FileHandle { file, writable })) + }); + + this.try_unwrap_io_result(fd) + } + + fn fcntl(&mut self, args: &[OpTy<'tcx, Provenance>]) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + if args.len() < 2 { + throw_ub_format!( + "incorrect number of arguments for fcntl: got {}, expected at least 2", + args.len() + ); + } + let fd = this.read_scalar(&args[0])?.to_i32()?; + let cmd = this.read_scalar(&args[1])?.to_i32()?; + + // Reject if isolation is enabled. + if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { + this.reject_in_isolation("`fcntl`", reject_with)?; + this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?; + return Ok(-1); + } + + // We only support getting the flags for a descriptor. + if cmd == this.eval_libc_i32("F_GETFD")? { + // Currently this is the only flag that `F_GETFD` returns. It is OK to just return the + // `FD_CLOEXEC` value without checking if the flag is set for the file because `std` + // always sets this flag when opening a file. However we still need to check that the + // file itself is open. + if this.machine.file_handler.handles.contains_key(&fd) { + Ok(this.eval_libc_i32("FD_CLOEXEC")?) + } else { + this.handle_not_found() + } + } else if cmd == this.eval_libc_i32("F_DUPFD")? + || cmd == this.eval_libc_i32("F_DUPFD_CLOEXEC")? + { + // Note that we always assume the FD_CLOEXEC flag is set for every open file, in part + // because exec() isn't supported. The F_DUPFD and F_DUPFD_CLOEXEC commands only + // differ in whether the FD_CLOEXEC flag is pre-set on the new file descriptor, + // thus they can share the same implementation here. + if args.len() < 3 { + throw_ub_format!( + "incorrect number of arguments for fcntl with cmd=`F_DUPFD`/`F_DUPFD_CLOEXEC`: got {}, expected at least 3", + args.len() + ); + } + let start = this.read_scalar(&args[2])?.to_i32()?; + + let fh = &mut this.machine.file_handler; + + match fh.handles.get_mut(&fd) { + Some(file_descriptor) => { + let dup_result = file_descriptor.dup(); + match dup_result { + Ok(dup_fd) => Ok(fh.insert_fd_with_min_fd(dup_fd, start)), + Err(e) => { + this.set_last_error_from_io_error(e.kind())?; + Ok(-1) + } + } + } + None => this.handle_not_found(), + } + } else if this.tcx.sess.target.os == "macos" && cmd == this.eval_libc_i32("F_FULLFSYNC")? { + if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) { + // FIXME: Support fullfsync for all FDs + let FileHandle { file, writable } = file_descriptor.as_file_handle()?; + let io_result = maybe_sync_file(file, *writable, File::sync_all); + this.try_unwrap_io_result(io_result) + } else { + this.handle_not_found() + } + } else { + throw_unsup_format!("the {:#x} command is not supported for `fcntl`)", cmd); + } + } + + fn close(&mut self, fd_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + + let fd = this.read_scalar(fd_op)?.to_i32()?; + + Ok(Scalar::from_i32( + if let Some(file_descriptor) = this.machine.file_handler.handles.remove(&fd) { + let result = file_descriptor.close(this.machine.communicate())?; + this.try_unwrap_io_result(result)? + } else { + this.handle_not_found()? + }, + )) + } + + fn read( + &mut self, + fd: i32, + buf: Pointer>, + count: u64, + ) -> InterpResult<'tcx, i64> { + let this = self.eval_context_mut(); + + // Isolation check is done via `FileDescriptor` trait. + + trace!("Reading from FD {}, size {}", fd, count); + + // Check that the *entire* buffer is actually valid memory. + this.check_ptr_access_align( + buf, + Size::from_bytes(count), + Align::ONE, + CheckInAllocMsg::MemoryAccessTest, + )?; + + // We cap the number of read bytes to the largest value that we are able to fit in both the + // host's and target's `isize`. This saves us from having to handle overflows later. + let count = count + .min(u64::try_from(this.machine_isize_max()).unwrap()) + .min(u64::try_from(isize::MAX).unwrap()); + let communicate = this.machine.communicate(); + + if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) { + trace!("read: FD mapped to {:?}", file_descriptor); + // We want to read at most `count` bytes. We are sure that `count` is not negative + // because it was a target's `usize`. Also we are sure that its smaller than + // `usize::MAX` because it is bounded by the host's `isize`. + let mut bytes = vec![0; usize::try_from(count).unwrap()]; + // `File::read` never returns a value larger than `count`, + // so this cannot fail. + let result = + file_descriptor.read(communicate, &mut bytes)?.map(|c| i64::try_from(c).unwrap()); + + match result { + Ok(read_bytes) => { + // If reading to `bytes` did not fail, we write those bytes to the buffer. + this.write_bytes_ptr(buf, bytes)?; + Ok(read_bytes) + } + Err(e) => { + this.set_last_error_from_io_error(e.kind())?; + Ok(-1) + } + } + } else { + trace!("read: FD not found"); + this.handle_not_found() + } + } + + fn write( + &mut self, + fd: i32, + buf: Pointer>, + count: u64, + ) -> InterpResult<'tcx, i64> { + let this = self.eval_context_mut(); + + // Isolation check is done via `FileDescriptor` trait. + + // Check that the *entire* buffer is actually valid memory. + this.check_ptr_access_align( + buf, + Size::from_bytes(count), + Align::ONE, + CheckInAllocMsg::MemoryAccessTest, + )?; + + // We cap the number of written bytes to the largest value that we are able to fit in both the + // host's and target's `isize`. This saves us from having to handle overflows later. + let count = count + .min(u64::try_from(this.machine_isize_max()).unwrap()) + .min(u64::try_from(isize::MAX).unwrap()); + let communicate = this.machine.communicate(); + + if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) { + let bytes = this.read_bytes_ptr_strip_provenance(buf, Size::from_bytes(count))?; + let result = + file_descriptor.write(communicate, bytes)?.map(|c| i64::try_from(c).unwrap()); + this.try_unwrap_io_result(result) + } else { + this.handle_not_found() + } + } + + fn lseek64( + &mut self, + fd_op: &OpTy<'tcx, Provenance>, + offset_op: &OpTy<'tcx, Provenance>, + whence_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + + // Isolation check is done via `FileDescriptor` trait. + + let fd = this.read_scalar(fd_op)?.to_i32()?; + let offset = this.read_scalar(offset_op)?.to_i64()?; + let whence = this.read_scalar(whence_op)?.to_i32()?; + + let seek_from = if whence == this.eval_libc_i32("SEEK_SET")? { + SeekFrom::Start(u64::try_from(offset).unwrap()) + } else if whence == this.eval_libc_i32("SEEK_CUR")? { + SeekFrom::Current(offset) + } else if whence == this.eval_libc_i32("SEEK_END")? { + SeekFrom::End(offset) + } else { + let einval = this.eval_libc("EINVAL")?; + this.set_last_error(einval)?; + return Ok(Scalar::from_i64(-1)); + }; + + let communicate = this.machine.communicate(); + Ok(Scalar::from_i64( + if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) { + let result = file_descriptor + .seek(communicate, seek_from)? + .map(|offset| i64::try_from(offset).unwrap()); + this.try_unwrap_io_result(result)? + } else { + this.handle_not_found()? + }, + )) + } + + fn unlink(&mut self, path_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?; + + // Reject if isolation is enabled. + if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { + this.reject_in_isolation("`unlink`", reject_with)?; + this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?; + return Ok(-1); + } + + let result = remove_file(path).map(|_| 0); + this.try_unwrap_io_result(result) + } + + fn symlink( + &mut self, + target_op: &OpTy<'tcx, Provenance>, + linkpath_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, i32> { + #[cfg(unix)] + fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> { + std::os::unix::fs::symlink(src, dst) + } + + #[cfg(windows)] + fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> { + use std::os::windows::fs; + if src.is_dir() { fs::symlink_dir(src, dst) } else { fs::symlink_file(src, dst) } + } + + let this = self.eval_context_mut(); + let target = this.read_path_from_c_str(this.read_pointer(target_op)?)?; + let linkpath = this.read_path_from_c_str(this.read_pointer(linkpath_op)?)?; + + // Reject if isolation is enabled. + if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { + this.reject_in_isolation("`symlink`", reject_with)?; + this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?; + return Ok(-1); + } + + let result = create_link(&target, &linkpath).map(|_| 0); + this.try_unwrap_io_result(result) + } + + fn macos_stat( + &mut self, + path_op: &OpTy<'tcx, Provenance>, + buf_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + this.assert_target_os("macos", "stat"); + + let path_scalar = this.read_pointer(path_op)?; + let path = this.read_path_from_c_str(path_scalar)?.into_owned(); + + // Reject if isolation is enabled. + if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { + this.reject_in_isolation("`stat`", reject_with)?; + let eacc = this.eval_libc("EACCES")?; + this.set_last_error(eacc)?; + return Ok(Scalar::from_i32(-1)); + } + + // `stat` always follows symlinks. + let metadata = match FileMetadata::from_path(this, &path, true)? { + Some(metadata) => metadata, + None => return Ok(Scalar::from_i32(-1)), // `FileMetadata` has set errno + }; + + Ok(Scalar::from_i32(this.macos_stat_write_buf(metadata, buf_op)?)) + } + + // `lstat` is used to get symlink metadata. + fn macos_lstat( + &mut self, + path_op: &OpTy<'tcx, Provenance>, + buf_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + this.assert_target_os("macos", "lstat"); + + let path_scalar = this.read_pointer(path_op)?; + let path = this.read_path_from_c_str(path_scalar)?.into_owned(); + + // Reject if isolation is enabled. + if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { + this.reject_in_isolation("`lstat`", reject_with)?; + let eacc = this.eval_libc("EACCES")?; + this.set_last_error(eacc)?; + return Ok(Scalar::from_i32(-1)); + } + + let metadata = match FileMetadata::from_path(this, &path, false)? { + Some(metadata) => metadata, + None => return Ok(Scalar::from_i32(-1)), // `FileMetadata` has set errno + }; + + Ok(Scalar::from_i32(this.macos_stat_write_buf(metadata, buf_op)?)) + } + + fn macos_fstat( + &mut self, + fd_op: &OpTy<'tcx, Provenance>, + buf_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + + this.assert_target_os("macos", "fstat"); + + let fd = this.read_scalar(fd_op)?.to_i32()?; + + // Reject if isolation is enabled. + if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { + this.reject_in_isolation("`fstat`", reject_with)?; + // Set error code as "EBADF" (bad fd) + return Ok(Scalar::from_i32(this.handle_not_found()?)); + } + + let metadata = match FileMetadata::from_fd(this, fd)? { + Some(metadata) => metadata, + None => return Ok(Scalar::from_i32(-1)), + }; + Ok(Scalar::from_i32(this.macos_stat_write_buf(metadata, buf_op)?)) + } + + fn linux_statx( + &mut self, + dirfd_op: &OpTy<'tcx, Provenance>, // Should be an `int` + pathname_op: &OpTy<'tcx, Provenance>, // Should be a `const char *` + flags_op: &OpTy<'tcx, Provenance>, // Should be an `int` + mask_op: &OpTy<'tcx, Provenance>, // Should be an `unsigned int` + statxbuf_op: &OpTy<'tcx, Provenance>, // Should be a `struct statx *` + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + this.assert_target_os("linux", "statx"); + + let dirfd = this.read_scalar(dirfd_op)?.to_i32()?; + let pathname_ptr = this.read_pointer(pathname_op)?; + let flags = this.read_scalar(flags_op)?.to_i32()?; + let _mask = this.read_scalar(mask_op)?.to_u32()?; + let statxbuf_ptr = this.read_pointer(statxbuf_op)?; + + // If the statxbuf or pathname pointers are null, the function fails with `EFAULT`. + if this.ptr_is_null(statxbuf_ptr)? || this.ptr_is_null(pathname_ptr)? { + let efault = this.eval_libc("EFAULT")?; + this.set_last_error(efault)?; + return Ok(-1); + } + + // Under normal circumstances, we would use `deref_operand(statxbuf_op)` to produce a + // proper `MemPlace` and then write the results of this function to it. However, the + // `syscall` function is untyped. This means that all the `statx` parameters are provided + // as `isize`s instead of having the proper types. Thus, we have to recover the layout of + // `statxbuf_op` by using the `libc::statx` struct type. + let statxbuf = { + // FIXME: This long path is required because `libc::statx` is an struct and also a + // function and `resolve_path` is returning the latter. + let statx_ty = this + .resolve_path(&["libc", "unix", "linux_like", "linux", "gnu", "statx"]) + .ty(*this.tcx, ty::ParamEnv::reveal_all()); + let statx_layout = this.layout_of(statx_ty)?; + MPlaceTy::from_aligned_ptr(statxbuf_ptr, statx_layout) + }; + + let path = this.read_path_from_c_str(pathname_ptr)?.into_owned(); + // See for a discussion of argument sizes. + let empty_path_flag = flags & this.eval_libc("AT_EMPTY_PATH")?.to_i32()? != 0; + // We only support: + // * interpreting `path` as an absolute directory, + // * interpreting `path` as a path relative to `dirfd` when the latter is `AT_FDCWD`, or + // * interpreting `dirfd` as any file descriptor when `path` is empty and AT_EMPTY_PATH is + // set. + // Other behaviors cannot be tested from `libstd` and thus are not implemented. If you + // found this error, please open an issue reporting it. + if !(path.is_absolute() + || dirfd == this.eval_libc_i32("AT_FDCWD")? + || (path.as_os_str().is_empty() && empty_path_flag)) + { + throw_unsup_format!( + "using statx is only supported with absolute paths, relative paths with the file \ + descriptor `AT_FDCWD`, and empty paths with the `AT_EMPTY_PATH` flag set and any \ + file descriptor" + ) + } + + // Reject if isolation is enabled. + if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { + this.reject_in_isolation("`statx`", reject_with)?; + let ecode = if path.is_absolute() || dirfd == this.eval_libc_i32("AT_FDCWD")? { + // since `path` is provided, either absolute or + // relative to CWD, `EACCES` is the most relevant. + this.eval_libc("EACCES")? + } else { + // `dirfd` is set to target file, and `path` is empty + // (or we would have hit the `throw_unsup_format` + // above). `EACCES` would violate the spec. + assert!(empty_path_flag); + this.eval_libc("EBADF")? + }; + this.set_last_error(ecode)?; + return Ok(-1); + } + + // the `_mask_op` paramter specifies the file information that the caller requested. + // However `statx` is allowed to return information that was not requested or to not + // return information that was requested. This `mask` represents the information we can + // actually provide for any target. + let mut mask = + this.eval_libc("STATX_TYPE")?.to_u32()? | this.eval_libc("STATX_SIZE")?.to_u32()?; + + // If the `AT_SYMLINK_NOFOLLOW` flag is set, we query the file's metadata without following + // symbolic links. + let follow_symlink = flags & this.eval_libc("AT_SYMLINK_NOFOLLOW")?.to_i32()? == 0; + + // If the path is empty, and the AT_EMPTY_PATH flag is set, we query the open file + // represented by dirfd, whether it's a directory or otherwise. + let metadata = if path.as_os_str().is_empty() && empty_path_flag { + FileMetadata::from_fd(this, dirfd)? + } else { + FileMetadata::from_path(this, &path, follow_symlink)? + }; + let metadata = match metadata { + Some(metadata) => metadata, + None => return Ok(-1), + }; + + // The `mode` field specifies the type of the file and the permissions over the file for + // the owner, its group and other users. Given that we can only provide the file type + // without using platform specific methods, we only set the bits corresponding to the file + // type. This should be an `__u16` but `libc` provides its values as `u32`. + let mode: u16 = metadata + .mode + .to_u32()? + .try_into() + .unwrap_or_else(|_| bug!("libc contains bad value for constant")); + + // We need to set the corresponding bits of `mask` if the access, creation and modification + // times were available. Otherwise we let them be zero. + let (access_sec, access_nsec) = metadata + .accessed + .map(|tup| { + mask |= this.eval_libc("STATX_ATIME")?.to_u32()?; + InterpResult::Ok(tup) + }) + .unwrap_or(Ok((0, 0)))?; + + let (created_sec, created_nsec) = metadata + .created + .map(|tup| { + mask |= this.eval_libc("STATX_BTIME")?.to_u32()?; + InterpResult::Ok(tup) + }) + .unwrap_or(Ok((0, 0)))?; + + let (modified_sec, modified_nsec) = metadata + .modified + .map(|tup| { + mask |= this.eval_libc("STATX_MTIME")?.to_u32()?; + InterpResult::Ok(tup) + }) + .unwrap_or(Ok((0, 0)))?; + + // Now we write everything to `statxbuf`. We write a zero for the unavailable fields. + this.write_int_fields_named( + &[ + ("stx_mask", mask.into()), + ("stx_blksize", 0), + ("stx_attributes", 0), + ("stx_nlink", 0), + ("stx_uid", 0), + ("stx_gid", 0), + ("stx_mode", mode.into()), + ("stx_ino", 0), + ("stx_size", metadata.size.into()), + ("stx_blocks", 0), + ("stx_attributes_mask", 0), + ("stx_rdev_major", 0), + ("stx_rdev_minor", 0), + ("stx_dev_major", 0), + ("stx_dev_minor", 0), + ], + &statxbuf, + )?; + #[rustfmt::skip] + this.write_int_fields_named( + &[ + ("tv_sec", access_sec.into()), + ("tv_nsec", access_nsec.into()), + ], + &this.mplace_field_named(&statxbuf, "stx_atime")?, + )?; + #[rustfmt::skip] + this.write_int_fields_named( + &[ + ("tv_sec", created_sec.into()), + ("tv_nsec", created_nsec.into()), + ], + &this.mplace_field_named(&statxbuf, "stx_btime")?, + )?; + #[rustfmt::skip] + this.write_int_fields_named( + &[ + ("tv_sec", 0.into()), + ("tv_nsec", 0.into()), + ], + &this.mplace_field_named(&statxbuf, "stx_ctime")?, + )?; + #[rustfmt::skip] + this.write_int_fields_named( + &[ + ("tv_sec", modified_sec.into()), + ("tv_nsec", modified_nsec.into()), + ], + &this.mplace_field_named(&statxbuf, "stx_mtime")?, + )?; + + Ok(0) + } + + fn rename( + &mut self, + oldpath_op: &OpTy<'tcx, Provenance>, + newpath_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + let oldpath_ptr = this.read_pointer(oldpath_op)?; + let newpath_ptr = this.read_pointer(newpath_op)?; + + if this.ptr_is_null(oldpath_ptr)? || this.ptr_is_null(newpath_ptr)? { + let efault = this.eval_libc("EFAULT")?; + this.set_last_error(efault)?; + return Ok(-1); + } + + let oldpath = this.read_path_from_c_str(oldpath_ptr)?; + let newpath = this.read_path_from_c_str(newpath_ptr)?; + + // Reject if isolation is enabled. + if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { + this.reject_in_isolation("`rename`", reject_with)?; + this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?; + return Ok(-1); + } + + let result = rename(oldpath, newpath).map(|_| 0); + + this.try_unwrap_io_result(result) + } + + fn mkdir( + &mut self, + path_op: &OpTy<'tcx, Provenance>, + mode_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + #[cfg_attr(not(unix), allow(unused_variables))] + let mode = if this.tcx.sess.target.os == "macos" { + u32::from(this.read_scalar(mode_op)?.to_u16()?) + } else { + this.read_scalar(mode_op)?.to_u32()? + }; + + let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?; + + // Reject if isolation is enabled. + if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { + this.reject_in_isolation("`mkdir`", reject_with)?; + this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?; + return Ok(-1); + } + + #[cfg_attr(not(unix), allow(unused_mut))] + let mut builder = DirBuilder::new(); + + // If the host supports it, forward on the mode of the directory + // (i.e. permission bits and the sticky bit) + #[cfg(unix)] + { + use std::os::unix::fs::DirBuilderExt; + builder.mode(mode); + } + + let result = builder.create(path).map(|_| 0i32); + + this.try_unwrap_io_result(result) + } + + fn rmdir(&mut self, path_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?; + + // Reject if isolation is enabled. + if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { + this.reject_in_isolation("`rmdir`", reject_with)?; + this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?; + return Ok(-1); + } + + let result = remove_dir(path).map(|_| 0i32); + + this.try_unwrap_io_result(result) + } + + fn opendir( + &mut self, + name_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + + let name = this.read_path_from_c_str(this.read_pointer(name_op)?)?; + + // Reject if isolation is enabled. + if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { + this.reject_in_isolation("`opendir`", reject_with)?; + let eacc = this.eval_libc("EACCES")?; + this.set_last_error(eacc)?; + return Ok(Scalar::null_ptr(this)); + } + + let result = read_dir(name); + + match result { + Ok(dir_iter) => { + let id = this.machine.dir_handler.insert_new(dir_iter); + + // The libc API for opendir says that this method returns a pointer to an opaque + // structure, but we are returning an ID number. Thus, pass it as a scalar of + // pointer width. + Ok(Scalar::from_machine_usize(id, this)) + } + Err(e) => { + this.set_last_error_from_io_error(e.kind())?; + Ok(Scalar::null_ptr(this)) + } + } + } + + fn linux_readdir64( + &mut self, + dirp_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + + this.assert_target_os("linux", "readdir64"); + + let dirp = this.read_scalar(dirp_op)?.to_machine_usize(this)?; + + // Reject if isolation is enabled. + if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { + this.reject_in_isolation("`readdir`", reject_with)?; + let eacc = this.eval_libc("EBADF")?; + this.set_last_error(eacc)?; + return Ok(Scalar::null_ptr(this)); + } + + let open_dir = this.machine.dir_handler.streams.get_mut(&dirp).ok_or_else(|| { + err_unsup_format!("the DIR pointer passed to readdir64 did not come from opendir") + })?; + + let entry = match open_dir.read_dir.next() { + Some(Ok(dir_entry)) => { + // Write the directory entry into a newly allocated buffer. + // The name is written with write_bytes, while the rest of the + // dirent64 struct is written using write_int_fields. + + // For reference: + // pub struct dirent64 { + // pub d_ino: ino64_t, + // pub d_off: off64_t, + // pub d_reclen: c_ushort, + // pub d_type: c_uchar, + // pub d_name: [c_char; 256], + // } + + let mut name = dir_entry.file_name(); // not a Path as there are no separators! + name.push("\0"); // Add a NUL terminator + let name_bytes = os_str_to_bytes(&name)?; + let name_len = u64::try_from(name_bytes.len()).unwrap(); + + let dirent64_layout = this.libc_ty_layout("dirent64")?; + let d_name_offset = dirent64_layout.fields.offset(4 /* d_name */).bytes(); + let size = d_name_offset.checked_add(name_len).unwrap(); + + let entry = + this.malloc(size, /*zero_init:*/ false, MiriMemoryKind::Runtime)?; + + // If the host is a Unix system, fill in the inode number with its real value. + // If not, use 0 as a fallback value. + #[cfg(unix)] + let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry); + #[cfg(not(unix))] + let ino = 0u64; + + let file_type = this.file_type_to_d_type(dir_entry.file_type())?; + + this.write_int_fields_named( + &[ + ("d_ino", ino.into()), + ("d_off", 0), + ("d_reclen", size.into()), + ("d_type", file_type.into()), + ], + &MPlaceTy::from_aligned_ptr(entry, dirent64_layout), + )?; + + let name_ptr = entry.offset(Size::from_bytes(d_name_offset), this)?; + this.write_bytes_ptr(name_ptr, name_bytes.iter().copied())?; + + entry + } + None => { + // end of stream: return NULL + Pointer::null() + } + Some(Err(e)) => { + this.set_last_error_from_io_error(e.kind())?; + Pointer::null() + } + }; + + let open_dir = this.machine.dir_handler.streams.get_mut(&dirp).unwrap(); + let old_entry = std::mem::replace(&mut open_dir.entry, entry); + this.free(old_entry, MiriMemoryKind::Runtime)?; + + Ok(Scalar::from_maybe_pointer(entry, this)) + } + + fn macos_readdir_r( + &mut self, + dirp_op: &OpTy<'tcx, Provenance>, + entry_op: &OpTy<'tcx, Provenance>, + result_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + + this.assert_target_os("macos", "readdir_r"); + + let dirp = this.read_scalar(dirp_op)?.to_machine_usize(this)?; + + // Reject if isolation is enabled. + if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { + this.reject_in_isolation("`readdir_r`", reject_with)?; + // Set error code as "EBADF" (bad fd) + return Ok(Scalar::from_i32(this.handle_not_found()?)); + } + + let open_dir = this.machine.dir_handler.streams.get_mut(&dirp).ok_or_else(|| { + err_unsup_format!("the DIR pointer passed to readdir_r did not come from opendir") + })?; + Ok(Scalar::from_i32(match open_dir.read_dir.next() { + Some(Ok(dir_entry)) => { + // Write into entry, write pointer to result, return 0 on success. + // The name is written with write_os_str_to_c_str, while the rest of the + // dirent struct is written using write_int_fields. + + // For reference: + // pub struct dirent { + // pub d_ino: u64, + // pub d_seekoff: u64, + // pub d_reclen: u16, + // pub d_namlen: u16, + // pub d_type: u8, + // pub d_name: [c_char; 1024], + // } + + let entry_place = this.deref_operand(entry_op)?; + let name_place = this.mplace_field(&entry_place, 5)?; + + let file_name = dir_entry.file_name(); // not a Path as there are no separators! + let (name_fits, file_name_buf_len) = this.write_os_str_to_c_str( + &file_name, + name_place.ptr, + name_place.layout.size.bytes(), + )?; + let file_name_len = file_name_buf_len.checked_sub(1).unwrap(); + if !name_fits { + throw_unsup_format!( + "a directory entry had a name too large to fit in libc::dirent" + ); + } + + let entry_place = this.deref_operand(entry_op)?; + + // If the host is a Unix system, fill in the inode number with its real value. + // If not, use 0 as a fallback value. + #[cfg(unix)] + let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry); + #[cfg(not(unix))] + let ino = 0u64; + + let file_type = this.file_type_to_d_type(dir_entry.file_type())?; + + this.write_int_fields_named( + &[ + ("d_ino", ino.into()), + ("d_seekoff", 0), + ("d_reclen", 0), + ("d_namlen", file_name_len.into()), + ("d_type", file_type.into()), + ], + &entry_place, + )?; + + let result_place = this.deref_operand(result_op)?; + this.write_scalar(this.read_scalar(entry_op)?, &result_place.into())?; + + 0 + } + None => { + // end of stream: return 0, assign *result=NULL + this.write_null(&this.deref_operand(result_op)?.into())?; + 0 + } + Some(Err(e)) => + match e.raw_os_error() { + // return positive error number on error + Some(error) => error, + None => { + throw_unsup_format!( + "the error {} couldn't be converted to a return value", + e + ) + } + }, + })) + } + + fn closedir(&mut self, dirp_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + let dirp = this.read_scalar(dirp_op)?.to_machine_usize(this)?; + + // Reject if isolation is enabled. + if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { + this.reject_in_isolation("`closedir`", reject_with)?; + // Set error code as "EBADF" (bad fd) + return this.handle_not_found(); + } + + if let Some(open_dir) = this.machine.dir_handler.streams.remove(&dirp) { + this.free(open_dir.entry, MiriMemoryKind::Runtime)?; + drop(open_dir); + Ok(0) + } else { + this.handle_not_found() + } + } + + fn ftruncate64( + &mut self, + fd_op: &OpTy<'tcx, Provenance>, + length_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + + let fd = this.read_scalar(fd_op)?.to_i32()?; + let length = this.read_scalar(length_op)?.to_i64()?; + + // Reject if isolation is enabled. + if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { + this.reject_in_isolation("`ftruncate64`", reject_with)?; + // Set error code as "EBADF" (bad fd) + return Ok(Scalar::from_i32(this.handle_not_found()?)); + } + + Ok(Scalar::from_i32( + if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) { + // FIXME: Support ftruncate64 for all FDs + let FileHandle { file, writable } = file_descriptor.as_file_handle()?; + if *writable { + if let Ok(length) = length.try_into() { + let result = file.set_len(length); + this.try_unwrap_io_result(result.map(|_| 0i32))? + } else { + let einval = this.eval_libc("EINVAL")?; + this.set_last_error(einval)?; + -1 + } + } else { + // The file is not writable + let einval = this.eval_libc("EINVAL")?; + this.set_last_error(einval)?; + -1 + } + } else { + this.handle_not_found()? + }, + )) + } + + fn fsync(&mut self, fd_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> { + // On macOS, `fsync` (unlike `fcntl(F_FULLFSYNC)`) does not wait for the + // underlying disk to finish writing. In the interest of host compatibility, + // we conservatively implement this with `sync_all`, which + // *does* wait for the disk. + + let this = self.eval_context_mut(); + + let fd = this.read_scalar(fd_op)?.to_i32()?; + + // Reject if isolation is enabled. + if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { + this.reject_in_isolation("`fsync`", reject_with)?; + // Set error code as "EBADF" (bad fd) + return this.handle_not_found(); + } + + if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) { + // FIXME: Support fsync for all FDs + let FileHandle { file, writable } = file_descriptor.as_file_handle()?; + let io_result = maybe_sync_file(file, *writable, File::sync_all); + this.try_unwrap_io_result(io_result) + } else { + this.handle_not_found() + } + } + + fn fdatasync(&mut self, fd_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + let fd = this.read_scalar(fd_op)?.to_i32()?; + + // Reject if isolation is enabled. + if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { + this.reject_in_isolation("`fdatasync`", reject_with)?; + // Set error code as "EBADF" (bad fd) + return this.handle_not_found(); + } + + if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) { + // FIXME: Support fdatasync for all FDs + let FileHandle { file, writable } = file_descriptor.as_file_handle()?; + let io_result = maybe_sync_file(file, *writable, File::sync_data); + this.try_unwrap_io_result(io_result) + } else { + this.handle_not_found() + } + } + + fn sync_file_range( + &mut self, + fd_op: &OpTy<'tcx, Provenance>, + offset_op: &OpTy<'tcx, Provenance>, + nbytes_op: &OpTy<'tcx, Provenance>, + flags_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + + let fd = this.read_scalar(fd_op)?.to_i32()?; + let offset = this.read_scalar(offset_op)?.to_i64()?; + let nbytes = this.read_scalar(nbytes_op)?.to_i64()?; + let flags = this.read_scalar(flags_op)?.to_i32()?; + + if offset < 0 || nbytes < 0 { + let einval = this.eval_libc("EINVAL")?; + this.set_last_error(einval)?; + return Ok(Scalar::from_i32(-1)); + } + let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE")? + | this.eval_libc_i32("SYNC_FILE_RANGE_WRITE")? + | this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER")?; + if flags & allowed_flags != flags { + let einval = this.eval_libc("EINVAL")?; + this.set_last_error(einval)?; + return Ok(Scalar::from_i32(-1)); + } + + // Reject if isolation is enabled. + if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { + this.reject_in_isolation("`sync_file_range`", reject_with)?; + // Set error code as "EBADF" (bad fd) + return Ok(Scalar::from_i32(this.handle_not_found()?)); + } + + if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) { + // FIXME: Support sync_data_range for all FDs + let FileHandle { file, writable } = file_descriptor.as_file_handle()?; + let io_result = maybe_sync_file(file, *writable, File::sync_data); + Ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?)) + } else { + Ok(Scalar::from_i32(this.handle_not_found()?)) + } + } + + fn readlink( + &mut self, + pathname_op: &OpTy<'tcx, Provenance>, + buf_op: &OpTy<'tcx, Provenance>, + bufsize_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, i64> { + let this = self.eval_context_mut(); + + let pathname = this.read_path_from_c_str(this.read_pointer(pathname_op)?)?; + let buf = this.read_pointer(buf_op)?; + let bufsize = this.read_scalar(bufsize_op)?.to_machine_usize(this)?; + + // Reject if isolation is enabled. + if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { + this.reject_in_isolation("`readlink`", reject_with)?; + let eacc = this.eval_libc("EACCES")?; + this.set_last_error(eacc)?; + return Ok(-1); + } + + let result = std::fs::read_link(pathname); + match result { + Ok(resolved) => { + // 'readlink' truncates the resolved path if the provided buffer is not large + // enough, and does *not* add a null terminator. That means we cannot use the usual + // `write_path_to_c_str` and have to re-implement parts of it ourselves. + let resolved = this.convert_path_separator( + Cow::Borrowed(resolved.as_ref()), + crate::shims::os_str::PathConversion::HostToTarget, + ); + let mut path_bytes = crate::shims::os_str::os_str_to_bytes(resolved.as_ref())?; + let bufsize: usize = bufsize.try_into().unwrap(); + if path_bytes.len() > bufsize { + path_bytes = &path_bytes[..bufsize] + } + this.write_bytes_ptr(buf, path_bytes.iter().copied())?; + Ok(path_bytes.len().try_into().unwrap()) + } + Err(e) => { + this.set_last_error_from_io_error(e.kind())?; + Ok(-1) + } + } + } + + #[cfg_attr(not(unix), allow(unused))] + fn isatty(&mut self, miri_fd: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + #[cfg(unix)] + if matches!(this.machine.isolated_op, IsolatedOp::Allow) { + let miri_fd = this.read_scalar(miri_fd)?.to_i32()?; + if let Some(host_fd) = + this.machine.file_handler.handles.get(&miri_fd).and_then(|fd| fd.as_unix_host_fd()) + { + // "returns 1 if fd is an open file descriptor referring to a terminal; + // otherwise 0 is returned, and errno is set to indicate the error" + // SAFETY: isatty has no preconditions + let is_tty = unsafe { libc::isatty(host_fd) }; + if is_tty == 0 { + let errno = std::io::Error::last_os_error() + .raw_os_error() + .map(Scalar::from_i32) + .unwrap(); + this.set_last_error(errno)?; + } + return Ok(is_tty); + } + } + // We are attemping to use a Unix interface on a non-Unix platform, or we are on a Unix + // platform and the passed file descriptor is not open, or isolation is enabled + // FIXME: It should be possible to emulate this at least on Windows by using + // GetConsoleMode. + let enotty = this.eval_libc("ENOTTY")?; + this.set_last_error(enotty)?; + Ok(0) + } + + fn realpath( + &mut self, + path_op: &OpTy<'tcx, Provenance>, + processed_path_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + this.assert_target_os_is_unix("realpath"); + + let pathname = this.read_path_from_c_str(this.read_pointer(path_op)?)?; + let processed_ptr = this.read_pointer(processed_path_op)?; + + // Reject if isolation is enabled. + if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { + this.reject_in_isolation("`realpath`", reject_with)?; + let eacc = this.eval_libc("EACCES")?; + this.set_last_error(eacc)?; + return Ok(Scalar::from_machine_usize(0, this)); + } + + let result = std::fs::canonicalize(pathname); + match result { + Ok(resolved) => { + let path_max = this + .eval_libc_i32("PATH_MAX")? + .try_into() + .expect("PATH_MAX does not fit in u64"); + let dest = if this.ptr_is_null(processed_ptr)? { + // POSIX says behavior when passing a null pointer is implementation-defined, + // but GNU/linux, freebsd, netbsd, bionic/android, and macos all treat a null pointer + // similarly to: + // + // "If resolved_path is specified as NULL, then realpath() uses + // malloc(3) to allocate a buffer of up to PATH_MAX bytes to hold + // the resolved pathname, and returns a pointer to this buffer. The + // caller should deallocate this buffer using free(3)." + // + this.alloc_path_as_c_str(&resolved, MiriMemoryKind::C.into())? + } else { + let (wrote_path, _) = + this.write_path_to_c_str(&resolved, processed_ptr, path_max)?; + + if !wrote_path { + // Note that we do not explicitly handle `FILENAME_MAX` + // (different from `PATH_MAX` above) as it is Linux-specific and + // seems like a bit of a mess anyway: . + let enametoolong = this.eval_libc("ENAMETOOLONG")?; + this.set_last_error(enametoolong)?; + return Ok(Scalar::from_machine_usize(0, this)); + } + processed_ptr + }; + + Ok(Scalar::from_maybe_pointer(dest, this)) + } + Err(e) => { + this.set_last_error_from_io_error(e.kind())?; + Ok(Scalar::from_machine_usize(0, this)) + } + } + } + fn mkstemp(&mut self, template_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> { + use rand::seq::SliceRandom; + + // POSIX defines the template string. + const TEMPFILE_TEMPLATE_STR: &str = "XXXXXX"; + + let this = self.eval_context_mut(); + this.assert_target_os_is_unix("mkstemp"); + + // POSIX defines the maximum number of attempts before failure. + // + // `mkstemp()` relies on `tmpnam()` which in turn relies on `TMP_MAX`. + // POSIX says this about `TMP_MAX`: + // * Minimum number of unique filenames generated by `tmpnam()`. + // * Maximum number of times an application can call `tmpnam()` reliably. + // * The value of `TMP_MAX` is at least 25. + // * On XSI-conformant systems, the value of `TMP_MAX` is at least 10000. + // See . + let max_attempts = this.eval_libc("TMP_MAX")?.to_u32()?; + + // Get the raw bytes from the template -- as a byte slice, this is a string in the target + // (and the target is unix, so a byte slice is the right representation). + let template_ptr = this.read_pointer(template_op)?; + let mut template = this.eval_context_ref().read_c_str(template_ptr)?.to_owned(); + let template_bytes = template.as_mut_slice(); + + // Reject if isolation is enabled. + if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { + this.reject_in_isolation("`mkstemp`", reject_with)?; + let eacc = this.eval_libc("EACCES")?; + this.set_last_error(eacc)?; + return Ok(-1); + } + + // Get the bytes of the suffix we expect in _target_ encoding. + let suffix_bytes = TEMPFILE_TEMPLATE_STR.as_bytes(); + + // At this point we have one `&[u8]` that represents the template and one `&[u8]` + // that represents the expected suffix. + + // Now we figure out the index of the slice we expect to contain the suffix. + let start_pos = template_bytes.len().saturating_sub(suffix_bytes.len()); + let end_pos = template_bytes.len(); + let last_six_char_bytes = &template_bytes[start_pos..end_pos]; + + // If we don't find the suffix, it is an error. + if last_six_char_bytes != suffix_bytes { + let einval = this.eval_libc("EINVAL")?; + this.set_last_error(einval)?; + return Ok(-1); + } + + // At this point we know we have 6 ASCII 'X' characters as a suffix. + + // From + const SUBSTITUTIONS: &[char; 62] = &[ + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', + 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', + 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + ]; + + // The file is opened with specific options, which Rust does not expose in a portable way. + // So we use specific APIs depending on the host OS. + let mut fopts = OpenOptions::new(); + fopts.read(true).write(true).create_new(true); + + #[cfg(unix)] + { + use std::os::unix::fs::OpenOptionsExt; + fopts.mode(0o600); + // Do not allow others to read or modify this file. + fopts.custom_flags(libc::O_EXCL); + } + #[cfg(windows)] + { + use std::os::windows::fs::OpenOptionsExt; + // Do not allow others to read or modify this file. + fopts.share_mode(0); + } + + // If the generated file already exists, we will try again `max_attempts` many times. + for _ in 0..max_attempts { + let rng = this.machine.rng.get_mut(); + + // Generate a random unique suffix. + let unique_suffix = SUBSTITUTIONS.choose_multiple(rng, 6).collect::(); + + // Replace the template string with the random string. + template_bytes[start_pos..end_pos].copy_from_slice(unique_suffix.as_bytes()); + + // Write the modified template back to the passed in pointer to maintain POSIX semantics. + this.write_bytes_ptr(template_ptr, template_bytes.iter().copied())?; + + // To actually open the file, turn this into a host OsString. + let p = bytes_to_os_str(template_bytes)?.to_os_string(); + + let possibly_unique = std::env::temp_dir().join::(p.into()); + + let file = fopts.open(&possibly_unique); + + match file { + Ok(f) => { + let fh = &mut this.machine.file_handler; + let fd = fh.insert_fd(Box::new(FileHandle { file: f, writable: true })); + return Ok(fd); + } + Err(e) => + match e.kind() { + // If the random file already exists, keep trying. + ErrorKind::AlreadyExists => continue, + // Any other errors are returned to the caller. + _ => { + // "On error, -1 is returned, and errno is set to + // indicate the error" + this.set_last_error_from_io_error(e.kind())?; + return Ok(-1); + } + }, + } + } + + // We ran out of attempts to create the file, return an error. + let eexist = this.eval_libc("EEXIST")?; + this.set_last_error(eexist)?; + Ok(-1) + } +} + +/// Extracts the number of seconds and nanoseconds elapsed between `time` and the unix epoch when +/// `time` is Ok. Returns `None` if `time` is an error. Fails if `time` happens before the unix +/// epoch. +fn extract_sec_and_nsec<'tcx>( + time: std::io::Result, +) -> InterpResult<'tcx, Option<(u64, u32)>> { + time.ok() + .map(|time| { + let duration = system_time_to_duration(&time)?; + Ok((duration.as_secs(), duration.subsec_nanos())) + }) + .transpose() +} + +/// Stores a file's metadata in order to avoid code duplication in the different metadata related +/// shims. +struct FileMetadata { + mode: Scalar, + size: u64, + created: Option<(u64, u32)>, + accessed: Option<(u64, u32)>, + modified: Option<(u64, u32)>, +} + +impl FileMetadata { + fn from_path<'tcx, 'mir>( + ecx: &mut MiriInterpCx<'mir, 'tcx>, + path: &Path, + follow_symlink: bool, + ) -> InterpResult<'tcx, Option> { + let metadata = + if follow_symlink { std::fs::metadata(path) } else { std::fs::symlink_metadata(path) }; + + FileMetadata::from_meta(ecx, metadata) + } + + fn from_fd<'tcx, 'mir>( + ecx: &mut MiriInterpCx<'mir, 'tcx>, + fd: i32, + ) -> InterpResult<'tcx, Option> { + let option = ecx.machine.file_handler.handles.get(&fd); + let file = match option { + Some(file_descriptor) => &file_descriptor.as_file_handle()?.file, + None => return ecx.handle_not_found().map(|_: i32| None), + }; + let metadata = file.metadata(); + + FileMetadata::from_meta(ecx, metadata) + } + + fn from_meta<'tcx, 'mir>( + ecx: &mut MiriInterpCx<'mir, 'tcx>, + metadata: Result, + ) -> InterpResult<'tcx, Option> { + let metadata = match metadata { + Ok(metadata) => metadata, + Err(e) => { + ecx.set_last_error_from_io_error(e.kind())?; + return Ok(None); + } + }; + + let file_type = metadata.file_type(); + + let mode_name = if file_type.is_file() { + "S_IFREG" + } else if file_type.is_dir() { + "S_IFDIR" + } else { + "S_IFLNK" + }; + + let mode = ecx.eval_libc(mode_name)?; + + let size = metadata.len(); + + let created = extract_sec_and_nsec(metadata.created())?; + let accessed = extract_sec_and_nsec(metadata.accessed())?; + let modified = extract_sec_and_nsec(metadata.modified())?; + + // FIXME: Provide more fields using platform specific methods. + Ok(Some(FileMetadata { mode, size, created, accessed, modified })) + } +} diff --git a/src/tools/miri/src/shims/unix/linux/dlsym.rs b/src/tools/miri/src/shims/unix/linux/dlsym.rs new file mode 100644 index 0000000000000..a96c14c142b25 --- /dev/null +++ b/src/tools/miri/src/shims/unix/linux/dlsym.rs @@ -0,0 +1,40 @@ +use rustc_middle::mir; + +use crate::*; + +#[derive(Debug, Copy, Clone)] +pub enum Dlsym {} + +impl Dlsym { + // Returns an error for unsupported symbols, and None if this symbol + // should become a NULL pointer (pretend it does not exist). + pub fn from_str<'tcx>(name: &str) -> InterpResult<'tcx, Option> { + Ok(match name { + "__pthread_get_minstack" => None, + "getrandom" => None, // std falls back to syscall(SYS_getrandom, ...) when this is NULL. + "statx" => None, // std falls back to syscall(SYS_statx, ...) when this is NULL. + _ => throw_unsup_format!("unsupported Linux dlsym: {}", name), + }) + } +} + +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + fn call_dlsym( + &mut self, + dlsym: Dlsym, + _args: &[OpTy<'tcx, Provenance>], + _dest: &PlaceTy<'tcx, Provenance>, + ret: Option, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + let _ret = ret.expect("we don't support any diverging dlsym"); + assert!(this.tcx.sess.target.os == "linux"); + + match dlsym {} + + //trace!("{:?}", this.dump_place(**dest)); + //this.go_to_block(ret); + //Ok(()) + } +} diff --git a/src/tools/miri/src/shims/unix/linux/foreign_items.rs b/src/tools/miri/src/shims/unix/linux/foreign_items.rs new file mode 100644 index 0000000000000..5d000f9d121d4 --- /dev/null +++ b/src/tools/miri/src/shims/unix/linux/foreign_items.rs @@ -0,0 +1,187 @@ +use rustc_span::Symbol; +use rustc_target::spec::abi::Abi; + +use crate::*; +use shims::foreign_items::EmulateByNameResult; +use shims::unix::fs::EvalContextExt as _; +use shims::unix::linux::sync::futex; +use shims::unix::sync::EvalContextExt as _; +use shims::unix::thread::EvalContextExt as _; + +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_by_name( + &mut self, + link_name: Symbol, + abi: Abi, + args: &[OpTy<'tcx, Provenance>], + dest: &PlaceTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, EmulateByNameResult<'mir, 'tcx>> { + let this = self.eval_context_mut(); + + // See `fn emulate_foreign_item_by_name` in `shims/foreign_items.rs` for the general pattern. + + match link_name.as_str() { + // errno + "__errno_location" => { + let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let errno_place = this.last_error_place()?; + this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?; + } + + // File related shims (but also see "syscall" below for statx) + "readdir64" => { + let [dirp] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.linux_readdir64(dirp)?; + this.write_scalar(result, dest)?; + } + // Linux-only + "sync_file_range" => { + let [fd, offset, nbytes, flags] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.sync_file_range(fd, offset, nbytes, flags)?; + this.write_scalar(result, dest)?; + } + + // Time related shims + "clock_gettime" => { + // This is a POSIX function but it has only been tested on linux. + let [clk_id, tp] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.clock_gettime(clk_id, tp)?; + this.write_scalar(result, dest)?; + } + + // Threading + "pthread_condattr_setclock" => { + let [attr, clock_id] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.pthread_condattr_setclock(attr, clock_id)?; + this.write_scalar(result, dest)?; + } + "pthread_condattr_getclock" => { + let [attr, clock_id] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.pthread_condattr_getclock(attr, clock_id)?; + this.write_scalar(result, dest)?; + } + "pthread_setname_np" => { + let [thread, name] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let res = + this.pthread_setname_np(this.read_scalar(thread)?, this.read_scalar(name)?)?; + this.write_scalar(res, dest)?; + } + + // Dynamically invoked syscalls + "syscall" => { + // We do not use `check_shim` here because `syscall` is variadic. The argument + // count is checked bellow. + this.check_abi_and_shim_symbol_clash(abi, Abi::C { unwind: false }, link_name)?; + // The syscall variadic function is legal to call with more arguments than needed, + // extra arguments are simply ignored. The important check is that when we use an + // argument, we have to also check all arguments *before* it to ensure that they + // have the right type. + + let sys_getrandom = this.eval_libc("SYS_getrandom")?.to_machine_usize(this)?; + + let sys_statx = this.eval_libc("SYS_statx")?.to_machine_usize(this)?; + + let sys_futex = this.eval_libc("SYS_futex")?.to_machine_usize(this)?; + + if args.is_empty() { + throw_ub_format!( + "incorrect number of arguments for syscall: got 0, expected at least 1" + ); + } + match this.read_scalar(&args[0])?.to_machine_usize(this)? { + // `libc::syscall(NR_GETRANDOM, buf.as_mut_ptr(), buf.len(), GRND_NONBLOCK)` + // is called if a `HashMap` is created the regular way (e.g. HashMap). + id if id == sys_getrandom => { + // The first argument is the syscall id, so skip over it. + if args.len() < 4 { + throw_ub_format!( + "incorrect number of arguments for `getrandom` syscall: got {}, expected at least 4", + args.len() + ); + } + getrandom(this, &args[1], &args[2], &args[3], dest)?; + } + // `statx` is used by `libstd` to retrieve metadata information on `linux` + // instead of using `stat`,`lstat` or `fstat` as on `macos`. + id if id == sys_statx => { + // The first argument is the syscall id, so skip over it. + if args.len() < 6 { + throw_ub_format!( + "incorrect number of arguments for `statx` syscall: got {}, expected at least 6", + args.len() + ); + } + let result = + this.linux_statx(&args[1], &args[2], &args[3], &args[4], &args[5])?; + this.write_scalar(Scalar::from_machine_isize(result.into(), this), dest)?; + } + // `futex` is used by some synchonization primitives. + id if id == sys_futex => { + futex(this, &args[1..], dest)?; + } + id => { + this.handle_unsupported(format!("can't execute syscall with ID {}", id))?; + return Ok(EmulateByNameResult::AlreadyJumped); + } + } + } + + // Miscelanneous + "getrandom" => { + let [ptr, len, flags] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + getrandom(this, ptr, len, flags, dest)?; + } + "sched_getaffinity" => { + let [pid, cpusetsize, mask] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + this.read_scalar(pid)?.to_i32()?; + this.read_scalar(cpusetsize)?.to_machine_usize(this)?; + this.deref_operand(mask)?; + // FIXME: we just return an error; `num_cpus` then falls back to `sysconf`. + let einval = this.eval_libc("EINVAL")?; + this.set_last_error(einval)?; + this.write_scalar(Scalar::from_i32(-1), 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. + "pthread_getattr_np" if this.frame_in_std() => { + let [_thread, _attr] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + this.write_null(dest)?; + } + + _ => return Ok(EmulateByNameResult::NotSupported), + }; + + Ok(EmulateByNameResult::NeedsJumping) + } +} + +// Shims the linux `getrandom` syscall. +fn getrandom<'tcx>( + this: &mut MiriInterpCx<'_, 'tcx>, + ptr: &OpTy<'tcx, Provenance>, + len: &OpTy<'tcx, Provenance>, + flags: &OpTy<'tcx, Provenance>, + dest: &PlaceTy<'tcx, Provenance>, +) -> InterpResult<'tcx> { + let ptr = this.read_pointer(ptr)?; + let len = this.read_scalar(len)?.to_machine_usize(this)?; + + // The only supported flags are GRND_RANDOM and GRND_NONBLOCK, + // neither of which have any effect on our current PRNG. + // See for a discussion of argument sizes. + let _flags = this.read_scalar(flags)?.to_i32(); + + this.gen_random(ptr, len)?; + this.write_scalar(Scalar::from_machine_usize(len, this), dest)?; + Ok(()) +} diff --git a/src/tools/miri/src/shims/unix/linux/mod.rs b/src/tools/miri/src/shims/unix/linux/mod.rs new file mode 100644 index 0000000000000..498eb57c57fe0 --- /dev/null +++ b/src/tools/miri/src/shims/unix/linux/mod.rs @@ -0,0 +1,3 @@ +pub mod dlsym; +pub mod foreign_items; +pub mod sync; diff --git a/src/tools/miri/src/shims/unix/linux/sync.rs b/src/tools/miri/src/shims/unix/linux/sync.rs new file mode 100644 index 0000000000000..5a6ce28d25c9f --- /dev/null +++ b/src/tools/miri/src/shims/unix/linux/sync.rs @@ -0,0 +1,261 @@ +use crate::concurrency::thread::Time; +use crate::*; +use rustc_target::abi::{Align, Size}; +use std::time::SystemTime; + +/// Implementation of the SYS_futex syscall. +/// `args` is the arguments *after* the syscall number. +pub fn futex<'tcx>( + this: &mut MiriInterpCx<'_, 'tcx>, + args: &[OpTy<'tcx, Provenance>], + dest: &PlaceTy<'tcx, Provenance>, +) -> InterpResult<'tcx> { + // The amount of arguments used depends on the type of futex operation. + // The full futex syscall takes six arguments (excluding the syscall + // number), which is also the maximum amount of arguments a linux syscall + // can take on most architectures. + // However, not all futex operations use all six arguments. The unused ones + // may or may not be left out from the `syscall()` call. + // Therefore we don't use `check_arg_count` here, but only check for the + // number of arguments to fall within a range. + if args.len() < 3 { + throw_ub_format!( + "incorrect number of arguments for `futex` syscall: got {}, expected at least 3", + args.len() + ); + } + + // The first three arguments (after the syscall number itself) are the same to all futex operations: + // (int *addr, int op, int val). + // We checked above that these definitely exist. + let addr = this.read_immediate(&args[0])?; + let op = this.read_scalar(&args[1])?.to_i32()?; + let val = this.read_scalar(&args[2])?.to_i32()?; + + let thread = this.get_active_thread(); + let addr_scalar = addr.to_scalar(); + let addr_usize = addr_scalar.to_machine_usize(this)?; + + let futex_private = this.eval_libc_i32("FUTEX_PRIVATE_FLAG")?; + let futex_wait = this.eval_libc_i32("FUTEX_WAIT")?; + let futex_wait_bitset = this.eval_libc_i32("FUTEX_WAIT_BITSET")?; + let futex_wake = this.eval_libc_i32("FUTEX_WAKE")?; + let futex_wake_bitset = this.eval_libc_i32("FUTEX_WAKE_BITSET")?; + let futex_realtime = this.eval_libc_i32("FUTEX_CLOCK_REALTIME")?; + + // FUTEX_PRIVATE enables an optimization that stops it from working across processes. + // Miri doesn't support that anyway, so we ignore that flag. + match op & !futex_private { + // FUTEX_WAIT: (int *addr, int op = FUTEX_WAIT, int val, const timespec *timeout) + // Blocks the thread if *addr still equals val. Wakes up when FUTEX_WAKE is called on the same address, + // or *timeout expires. `timeout == null` for an infinite timeout. + // + // FUTEX_WAIT_BITSET: (int *addr, int op = FUTEX_WAIT_BITSET, int val, const timespec *timeout, int *_ignored, unsigned int bitset) + // This is identical to FUTEX_WAIT, except: + // - The timeout is absolute rather than relative. + // - You can specify the bitset to selecting what WAKE operations to respond to. + op if op & !futex_realtime == futex_wait || op & !futex_realtime == futex_wait_bitset => { + let wait_bitset = op & !futex_realtime == futex_wait_bitset; + + let bitset = if wait_bitset { + if args.len() < 6 { + throw_ub_format!( + "incorrect number of arguments for `futex` syscall with `op=FUTEX_WAIT_BITSET`: got {}, expected at least 6", + args.len() + ); + } + let _timeout = this.read_pointer(&args[3])?; + let _uaddr2 = this.read_pointer(&args[4])?; + this.read_scalar(&args[5])?.to_u32()? + } else { + if args.len() < 4 { + throw_ub_format!( + "incorrect number of arguments for `futex` syscall with `op=FUTEX_WAIT`: got {}, expected at least 4", + args.len() + ); + } + u32::MAX + }; + + if bitset == 0 { + let einval = this.eval_libc("EINVAL")?; + this.set_last_error(einval)?; + this.write_scalar(Scalar::from_machine_isize(-1, this), dest)?; + return Ok(()); + } + + // `deref_operand` but not actually dereferencing the ptr yet (it might be NULL!). + let timeout = this.ref_to_mplace(&this.read_immediate(&args[3])?)?; + let timeout_time = if this.ptr_is_null(timeout.ptr)? { + None + } else { + this.check_no_isolation( + "`futex` syscall with `op=FUTEX_WAIT` and non-null timeout", + )?; + let duration = match this.read_timespec(&timeout)? { + Some(duration) => duration, + None => { + let einval = this.eval_libc("EINVAL")?; + this.set_last_error(einval)?; + this.write_scalar(Scalar::from_machine_isize(-1, this), dest)?; + return Ok(()); + } + }; + Some(if wait_bitset { + // FUTEX_WAIT_BITSET uses an absolute timestamp. + if op & futex_realtime != 0 { + Time::RealTime(SystemTime::UNIX_EPOCH.checked_add(duration).unwrap()) + } else { + Time::Monotonic(this.machine.clock.anchor().checked_add(duration).unwrap()) + } + } else { + // FUTEX_WAIT uses a relative timestamp. + if op & futex_realtime != 0 { + Time::RealTime(SystemTime::now().checked_add(duration).unwrap()) + } else { + Time::Monotonic(this.machine.clock.now().checked_add(duration).unwrap()) + } + }) + }; + // Check the pointer for alignment and validity. + // The API requires `addr` to be a 4-byte aligned pointer, and will + // use the 4 bytes at the given address as an (atomic) i32. + this.check_ptr_access_align( + addr_scalar.to_pointer(this)?, + Size::from_bytes(4), + Align::from_bytes(4).unwrap(), + CheckInAllocMsg::MemoryAccessTest, + )?; + // There may be a concurrent thread changing the value of addr + // and then invoking the FUTEX_WAKE syscall. It is critical that the + // effects of this and the other thread are correctly observed, + // otherwise we will deadlock. + // + // There are two scenarios to consider: + // 1. If we (FUTEX_WAIT) execute first, we'll push ourselves into + // the waiters queue and go to sleep. They (addr write & FUTEX_WAKE) + // will see us in the queue and wake us up. + // 2. If they (addr write & FUTEX_WAKE) execute first, we must observe + // addr's new value. If we see an outdated value that happens to equal + // the expected val, then we'll put ourselves to sleep with no one to wake us + // up, so we end up with a deadlock. This is prevented by having a SeqCst + // fence inside FUTEX_WAKE syscall, and another SeqCst fence + // below, the atomic read on addr after the SeqCst fence is guaranteed + // not to see any value older than the addr write immediately before + // calling FUTEX_WAKE. We'll see futex_val != val and return without + // sleeping. + // + // Note that the fences do not create any happens-before relationship. + // The read sees the write immediately before the fence not because + // one happens after the other, but is instead due to a guarantee unique + // to SeqCst fences that restricts what an atomic read placed AFTER the + // fence can see. The read still has to be atomic, otherwise it's a data + // race. This guarantee cannot be achieved with acquire-release fences + // since they only talk about reads placed BEFORE a fence - and places + // no restrictions on what the read itself can see, only that there is + // a happens-before between the fences IF the read happens to see the + // right value. This is useless to us, since we need the read itself + // to see an up-to-date value. + // + // The above case distinction is valid since both FUTEX_WAIT and FUTEX_WAKE + // contain a SeqCst fence, therefore inducting a total order between the operations. + // It is also critical that the fence, the atomic load, and the comparison in FUTEX_WAIT + // altogether happen atomically. If the other thread's fence in FUTEX_WAKE + // gets interleaved after our fence, then we lose the guarantee on the + // atomic load being up-to-date; if the other thread's write on addr and FUTEX_WAKE + // call are interleaved after the load but before the comparison, then we get a TOCTOU + // race condition, and go to sleep thinking the other thread will wake us up, + // even though they have already finished. + // + // Thankfully, preemptions cannot happen inside a Miri shim, so we do not need to + // do anything special to guarantee fence-load-comparison atomicity. + this.atomic_fence(AtomicFenceOrd::SeqCst)?; + // Read an `i32` through the pointer, regardless of any wrapper types. + // It's not uncommon for `addr` to be passed as another type than `*mut i32`, such as `*const AtomicI32`. + let futex_val = this + .read_scalar_at_offset_atomic( + &addr.into(), + 0, + this.machine.layouts.i32, + AtomicReadOrd::Relaxed, + )? + .to_i32()?; + if val == futex_val { + // The value still matches, so we block the thread make it wait for FUTEX_WAKE. + this.block_thread(thread); + this.futex_wait(addr_usize, thread, bitset); + // Succesfully waking up from FUTEX_WAIT always returns zero. + this.write_scalar(Scalar::from_machine_isize(0, this), dest)?; + // Register a timeout callback if a timeout was specified. + // This callback will override the return value when the timeout triggers. + if let Some(timeout_time) = timeout_time { + let dest = dest.clone(); + this.register_timeout_callback( + thread, + timeout_time, + Box::new(move |this| { + this.unblock_thread(thread); + this.futex_remove_waiter(addr_usize, thread); + let etimedout = this.eval_libc("ETIMEDOUT")?; + this.set_last_error(etimedout)?; + this.write_scalar(Scalar::from_machine_isize(-1, this), &dest)?; + Ok(()) + }), + ); + } + } else { + // The futex value doesn't match the expected value, so we return failure + // right away without sleeping: -1 and errno set to EAGAIN. + let eagain = this.eval_libc("EAGAIN")?; + this.set_last_error(eagain)?; + this.write_scalar(Scalar::from_machine_isize(-1, this), dest)?; + } + } + // FUTEX_WAKE: (int *addr, int op = FUTEX_WAKE, int val) + // Wakes at most `val` threads waiting on the futex at `addr`. + // Returns the amount of threads woken up. + // Does not access the futex value at *addr. + // FUTEX_WAKE_BITSET: (int *addr, int op = FUTEX_WAKE, int val, const timespect *_unused, int *_unused, unsigned int bitset) + // Same as FUTEX_WAKE, but allows you to specify a bitset to select which threads to wake up. + op if op == futex_wake || op == futex_wake_bitset => { + let bitset = if op == futex_wake_bitset { + if args.len() < 6 { + throw_ub_format!( + "incorrect number of arguments for `futex` syscall with `op=FUTEX_WAKE_BITSET`: got {}, expected at least 6", + args.len() + ); + } + let _timeout = this.read_pointer(&args[3])?; + let _uaddr2 = this.read_pointer(&args[4])?; + this.read_scalar(&args[5])?.to_u32()? + } else { + u32::MAX + }; + if bitset == 0 { + let einval = this.eval_libc("EINVAL")?; + this.set_last_error(einval)?; + this.write_scalar(Scalar::from_machine_isize(-1, this), dest)?; + return Ok(()); + } + // Together with the SeqCst fence in futex_wait, this makes sure that futex_wait + // will see the latest value on addr which could be changed by our caller + // before doing the syscall. + this.atomic_fence(AtomicFenceOrd::SeqCst)?; + let mut n = 0; + #[allow(clippy::integer_arithmetic)] + for _ in 0..val { + if let Some(thread) = this.futex_wake(addr_usize, bitset) { + this.unblock_thread(thread); + this.unregister_timeout_callback_if_exists(thread); + n += 1; + } else { + break; + } + } + this.write_scalar(Scalar::from_machine_isize(n, this), dest)?; + } + op => throw_unsup_format!("Miri does not support `futex` syscall with op={}", op), + } + + Ok(()) +} diff --git a/src/tools/miri/src/shims/unix/macos/dlsym.rs b/src/tools/miri/src/shims/unix/macos/dlsym.rs new file mode 100644 index 0000000000000..18804b45efca9 --- /dev/null +++ b/src/tools/miri/src/shims/unix/macos/dlsym.rs @@ -0,0 +1,52 @@ +use rustc_middle::mir; + +use log::trace; + +use crate::*; +use helpers::check_arg_count; + +#[derive(Debug, Copy, Clone)] +#[allow(non_camel_case_types)] +pub enum Dlsym { + getentropy, +} + +impl Dlsym { + // Returns an error for unsupported symbols, and None if this symbol + // should become a NULL pointer (pretend it does not exist). + pub fn from_str<'tcx>(name: &str) -> InterpResult<'tcx, Option> { + Ok(match name { + "getentropy" => Some(Dlsym::getentropy), + _ => throw_unsup_format!("unsupported macOS dlsym: {}", name), + }) + } +} + +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + fn call_dlsym( + &mut self, + dlsym: Dlsym, + args: &[OpTy<'tcx, Provenance>], + dest: &PlaceTy<'tcx, Provenance>, + ret: Option, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + let ret = ret.expect("we don't support any diverging dlsym"); + assert!(this.tcx.sess.target.os == "macos"); + + match dlsym { + Dlsym::getentropy => { + let [ptr, len] = check_arg_count(args)?; + let ptr = this.read_pointer(ptr)?; + let len = this.read_scalar(len)?.to_machine_usize(this)?; + this.gen_random(ptr, len)?; + this.write_null(dest)?; + } + } + + trace!("{:?}", this.dump_place(**dest)); + this.go_to_block(ret); + Ok(()) + } +} diff --git a/src/tools/miri/src/shims/unix/macos/foreign_items.rs b/src/tools/miri/src/shims/unix/macos/foreign_items.rs new file mode 100644 index 0000000000000..38d791fba98a4 --- /dev/null +++ b/src/tools/miri/src/shims/unix/macos/foreign_items.rs @@ -0,0 +1,197 @@ +use rustc_span::Symbol; +use rustc_target::spec::abi::Abi; + +use crate::*; +use shims::foreign_items::EmulateByNameResult; +use shims::unix::fs::EvalContextExt as _; +use shims::unix::thread::EvalContextExt as _; + +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_by_name( + &mut self, + link_name: Symbol, + abi: Abi, + args: &[OpTy<'tcx, Provenance>], + dest: &PlaceTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, EmulateByNameResult<'mir, 'tcx>> { + let this = self.eval_context_mut(); + + // See `fn emulate_foreign_item_by_name` in `shims/foreign_items.rs` for the general pattern. + + match link_name.as_str() { + // errno + "__error" => { + let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let errno_place = this.last_error_place()?; + this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?; + } + + // File related shims + "close$NOCANCEL" => { + let [result] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.close(result)?; + this.write_scalar(result, dest)?; + } + "stat" | "stat64" | "stat$INODE64" => { + let [path, buf] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.macos_stat(path, buf)?; + this.write_scalar(result, dest)?; + } + "lstat" | "lstat64" | "lstat$INODE64" => { + let [path, buf] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.macos_lstat(path, buf)?; + this.write_scalar(result, dest)?; + } + "fstat" | "fstat64" | "fstat$INODE64" => { + let [fd, buf] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.macos_fstat(fd, buf)?; + this.write_scalar(result, dest)?; + } + "opendir$INODE64" => { + let [name] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.opendir(name)?; + this.write_scalar(result, dest)?; + } + "readdir_r" | "readdir_r$INODE64" => { + let [dirp, entry, result] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.macos_readdir_r(dirp, entry, result)?; + this.write_scalar(result, dest)?; + } + "lseek" => { + let [fd, offset, whence] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + // macOS is 64bit-only, so this is lseek64 + let result = this.lseek64(fd, offset, whence)?; + this.write_scalar(result, dest)?; + } + "ftruncate" => { + let [fd, length] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + // macOS is 64bit-only, so this is ftruncate64 + let result = this.ftruncate64(fd, length)?; + this.write_scalar(result, dest)?; + } + "realpath$DARWIN_EXTSN" => { + let [path, resolved_path] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.realpath(path, resolved_path)?; + this.write_scalar(result, dest)?; + } + + // Environment related shims + "_NSGetEnviron" => { + let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + this.write_pointer( + this.machine.env_vars.environ.expect("machine must be initialized").ptr, + dest, + )?; + } + + // Time related shims + "mach_absolute_time" => { + let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.mach_absolute_time()?; + this.write_scalar(result, dest)?; + } + + "mach_timebase_info" => { + let [info] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.mach_timebase_info(info)?; + this.write_scalar(result, dest)?; + } + + // Access to command-line arguments + "_NSGetArgc" => { + let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + this.write_pointer( + this.machine.argc.expect("machine must be initialized").ptr, + dest, + )?; + } + "_NSGetArgv" => { + let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + this.write_pointer( + this.machine.argv.expect("machine must be initialized").ptr, + dest, + )?; + } + "_NSGetExecutablePath" => { + let [buf, bufsize] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + this.check_no_isolation("`_NSGetExecutablePath`")?; + + let buf_ptr = this.read_pointer(buf)?; + let bufsize = this.deref_operand(bufsize)?; + + // Using the host current_exe is a bit off, but consistent with Linux + // (where stdlib reads /proc/self/exe). + let path = std::env::current_exe().unwrap(); + let (written, size_needed) = this.write_path_to_c_str( + &path, + buf_ptr, + this.read_scalar(&bufsize.into())?.to_u32()?.into(), + )?; + + if written { + this.write_null(dest)?; + } else { + this.write_scalar( + Scalar::from_u32(size_needed.try_into().unwrap()), + &bufsize.into(), + )?; + this.write_int(-1, dest)?; + } + } + + // Thread-local storage + "_tlv_atexit" => { + let [dtor, data] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let dtor = this.read_pointer(dtor)?; + let dtor = this.get_ptr_fn(dtor)?.as_instance()?; + let data = this.read_scalar(data)?; + let active_thread = this.get_active_thread(); + this.machine.tls.set_macos_thread_dtor(active_thread, dtor, data)?; + } + + // Querying system information + "pthread_get_stackaddr_np" => { + let [thread] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + this.read_scalar(thread)?.to_machine_usize(this)?; + let stack_addr = Scalar::from_uint(STACK_ADDR, this.pointer_size()); + this.write_scalar(stack_addr, dest)?; + } + "pthread_get_stacksize_np" => { + let [thread] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + this.read_scalar(thread)?.to_machine_usize(this)?; + let stack_size = Scalar::from_uint(STACK_SIZE, this.pointer_size()); + this.write_scalar(stack_size, dest)?; + } + + // Threading + "pthread_setname_np" => { + let [name] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let thread = this.pthread_self()?; + this.pthread_setname_np(thread, this.read_scalar(name)?)?; + } + + // 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. + "mmap" if this.frame_in_std() => { + // This is a horrible hack, but since the guard page mechanism calls mmap and expects a particular return value, we just give it that value. + let [addr, _, _, _, _, _] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let addr = this.read_scalar(addr)?; + this.write_scalar(addr, dest)?; + } + + _ => return Ok(EmulateByNameResult::NotSupported), + }; + + Ok(EmulateByNameResult::NeedsJumping) + } +} diff --git a/src/tools/miri/src/shims/unix/macos/mod.rs b/src/tools/miri/src/shims/unix/macos/mod.rs new file mode 100644 index 0000000000000..434f5f30b5a56 --- /dev/null +++ b/src/tools/miri/src/shims/unix/macos/mod.rs @@ -0,0 +1,2 @@ +pub mod dlsym; +pub mod foreign_items; diff --git a/src/tools/miri/src/shims/unix/mod.rs b/src/tools/miri/src/shims/unix/mod.rs new file mode 100644 index 0000000000000..6fefb054f3c04 --- /dev/null +++ b/src/tools/miri/src/shims/unix/mod.rs @@ -0,0 +1,16 @@ +pub mod dlsym; +pub mod foreign_items; + +mod fs; +mod sync; +mod thread; + +mod android; +mod freebsd; +mod linux; +mod macos; + +pub use fs::{DirHandler, FileHandler}; + +// Make up some constants. +const UID: u32 = 1000; diff --git a/src/tools/miri/src/shims/unix/sync.rs b/src/tools/miri/src/shims/unix/sync.rs new file mode 100644 index 0000000000000..2e972a27ffebe --- /dev/null +++ b/src/tools/miri/src/shims/unix/sync.rs @@ -0,0 +1,907 @@ +use std::time::SystemTime; + +use rustc_hir::LangItem; +use rustc_middle::ty::{layout::TyAndLayout, query::TyCtxtAt, Ty}; + +use crate::concurrency::thread::Time; +use crate::*; + +// pthread_mutexattr_t is either 4 or 8 bytes, depending on the platform. + +// Our chosen memory layout for emulation (does not have to match the platform layout!): +// store an i32 in the first four bytes equal to the corresponding libc mutex kind constant +// (e.g. PTHREAD_MUTEX_NORMAL). + +/// A flag that allows to distinguish `PTHREAD_MUTEX_NORMAL` from +/// `PTHREAD_MUTEX_DEFAULT`. Since in `glibc` they have the same numeric values, +/// but different behaviour, we need a way to distinguish them. We do this by +/// setting this bit flag to the `PTHREAD_MUTEX_NORMAL` mutexes. See the comment +/// in `pthread_mutexattr_settype` function. +const PTHREAD_MUTEX_NORMAL_FLAG: i32 = 0x8000000; + +fn is_mutex_kind_default<'mir, 'tcx: 'mir>( + ecx: &mut MiriInterpCx<'mir, 'tcx>, + kind: Scalar, +) -> InterpResult<'tcx, bool> { + Ok(kind == ecx.eval_libc("PTHREAD_MUTEX_DEFAULT")?) +} + +fn is_mutex_kind_normal<'mir, 'tcx: 'mir>( + ecx: &mut MiriInterpCx<'mir, 'tcx>, + kind: Scalar, +) -> InterpResult<'tcx, bool> { + let kind = kind.to_i32()?; + let mutex_normal_kind = ecx.eval_libc("PTHREAD_MUTEX_NORMAL")?.to_i32()?; + Ok(kind == (mutex_normal_kind | PTHREAD_MUTEX_NORMAL_FLAG)) +} + +fn mutexattr_get_kind<'mir, 'tcx: 'mir>( + ecx: &MiriInterpCx<'mir, 'tcx>, + attr_op: &OpTy<'tcx, Provenance>, +) -> InterpResult<'tcx, Scalar> { + ecx.read_scalar_at_offset(attr_op, 0, ecx.machine.layouts.i32) +} + +fn mutexattr_set_kind<'mir, 'tcx: 'mir>( + ecx: &mut MiriInterpCx<'mir, 'tcx>, + attr_op: &OpTy<'tcx, Provenance>, + kind: impl Into>, +) -> InterpResult<'tcx, ()> { + ecx.write_scalar_at_offset(attr_op, 0, kind, layout_of_maybe_uninit(ecx.tcx, ecx.tcx.types.i32)) +} + +// pthread_mutex_t is between 24 and 48 bytes, depending on the platform. + +// Our chosen memory layout for the emulated mutex (does not have to match the platform layout!): +// bytes 0-3: reserved for signature on macOS +// (need to avoid this because it is set by static initializer macros) +// bytes 4-7: mutex id as u32 or 0 if id is not assigned yet. +// bytes 12-15 or 16-19 (depending on platform): mutex kind, as an i32 +// (the kind has to be at its offset for compatibility with static initializer macros) + +fn mutex_get_kind<'mir, 'tcx: 'mir>( + ecx: &MiriInterpCx<'mir, 'tcx>, + mutex_op: &OpTy<'tcx, Provenance>, +) -> InterpResult<'tcx, Scalar> { + let offset = if ecx.pointer_size().bytes() == 8 { 16 } else { 12 }; + ecx.read_scalar_at_offset_atomic( + mutex_op, + offset, + ecx.machine.layouts.i32, + AtomicReadOrd::Relaxed, + ) +} + +fn mutex_set_kind<'mir, 'tcx: 'mir>( + ecx: &mut MiriInterpCx<'mir, 'tcx>, + mutex_op: &OpTy<'tcx, Provenance>, + kind: impl Into>, +) -> InterpResult<'tcx, ()> { + let offset = if ecx.pointer_size().bytes() == 8 { 16 } else { 12 }; + ecx.write_scalar_at_offset_atomic( + mutex_op, + offset, + kind, + layout_of_maybe_uninit(ecx.tcx, ecx.tcx.types.i32), + AtomicWriteOrd::Relaxed, + ) +} + +fn mutex_get_id<'mir, 'tcx: 'mir>( + ecx: &MiriInterpCx<'mir, 'tcx>, + mutex_op: &OpTy<'tcx, Provenance>, +) -> InterpResult<'tcx, Scalar> { + ecx.read_scalar_at_offset_atomic(mutex_op, 4, ecx.machine.layouts.u32, AtomicReadOrd::Relaxed) +} + +fn mutex_set_id<'mir, 'tcx: 'mir>( + ecx: &mut MiriInterpCx<'mir, 'tcx>, + mutex_op: &OpTy<'tcx, Provenance>, + id: impl Into>, +) -> InterpResult<'tcx, ()> { + ecx.write_scalar_at_offset_atomic( + mutex_op, + 4, + id, + layout_of_maybe_uninit(ecx.tcx, ecx.tcx.types.u32), + AtomicWriteOrd::Relaxed, + ) +} + +fn mutex_get_or_create_id<'mir, 'tcx: 'mir>( + ecx: &mut MiriInterpCx<'mir, 'tcx>, + mutex_op: &OpTy<'tcx, Provenance>, +) -> InterpResult<'tcx, MutexId> { + let value_place = ecx.deref_operand_and_offset(mutex_op, 4, ecx.machine.layouts.u32)?; + + ecx.mutex_get_or_create(|ecx, next_id| { + let (old, success) = ecx + .atomic_compare_exchange_scalar( + &value_place, + &ImmTy::from_uint(0u32, ecx.machine.layouts.u32), + next_id.to_u32_scalar(), + AtomicRwOrd::Relaxed, + AtomicReadOrd::Relaxed, + false, + )? + .to_scalar_pair(); + + Ok(if success.to_bool().expect("compare_exchange's second return value is a bool") { + // Caller of the closure needs to allocate next_id + None + } else { + Some(MutexId::from_u32(old.to_u32().expect("layout is u32"))) + }) + }) +} + +// pthread_rwlock_t is between 32 and 56 bytes, depending on the platform. + +// Our chosen memory layout for the emulated rwlock (does not have to match the platform layout!): +// bytes 0-3: reserved for signature on macOS +// (need to avoid this because it is set by static initializer macros) +// bytes 4-7: rwlock id as u32 or 0 if id is not assigned yet. + +fn rwlock_get_id<'mir, 'tcx: 'mir>( + ecx: &MiriInterpCx<'mir, 'tcx>, + rwlock_op: &OpTy<'tcx, Provenance>, +) -> InterpResult<'tcx, Scalar> { + ecx.read_scalar_at_offset_atomic(rwlock_op, 4, ecx.machine.layouts.u32, AtomicReadOrd::Relaxed) +} + +fn rwlock_get_or_create_id<'mir, 'tcx: 'mir>( + ecx: &mut MiriInterpCx<'mir, 'tcx>, + rwlock_op: &OpTy<'tcx, Provenance>, +) -> InterpResult<'tcx, RwLockId> { + let value_place = ecx.deref_operand_and_offset(rwlock_op, 4, ecx.machine.layouts.u32)?; + + ecx.rwlock_get_or_create(|ecx, next_id| { + let (old, success) = ecx + .atomic_compare_exchange_scalar( + &value_place, + &ImmTy::from_uint(0u32, ecx.machine.layouts.u32), + next_id.to_u32_scalar(), + AtomicRwOrd::Relaxed, + AtomicReadOrd::Relaxed, + false, + )? + .to_scalar_pair(); + + Ok(if success.to_bool().expect("compare_exchange's second return value is a bool") { + // Caller of the closure needs to allocate next_id + None + } else { + Some(RwLockId::from_u32(old.to_u32().expect("layout is u32"))) + }) + }) +} + +// pthread_condattr_t + +// Our chosen memory layout for emulation (does not have to match the platform layout!): +// store an i32 in the first four bytes equal to the corresponding libc clock id constant +// (e.g. CLOCK_REALTIME). + +fn condattr_get_clock_id<'mir, 'tcx: 'mir>( + ecx: &MiriInterpCx<'mir, 'tcx>, + attr_op: &OpTy<'tcx, Provenance>, +) -> InterpResult<'tcx, Scalar> { + ecx.read_scalar_at_offset(attr_op, 0, ecx.machine.layouts.i32) +} + +fn condattr_set_clock_id<'mir, 'tcx: 'mir>( + ecx: &mut MiriInterpCx<'mir, 'tcx>, + attr_op: &OpTy<'tcx, Provenance>, + clock_id: impl Into>, +) -> InterpResult<'tcx, ()> { + ecx.write_scalar_at_offset( + attr_op, + 0, + clock_id, + layout_of_maybe_uninit(ecx.tcx, ecx.machine.layouts.i32.ty), + ) +} + +// pthread_cond_t + +// Our chosen memory layout for the emulated conditional variable (does not have +// to match the platform layout!): + +// bytes 0-3: reserved for signature on macOS +// bytes 4-7: the conditional variable id as u32 or 0 if id is not assigned yet. +// bytes 8-11: the clock id constant as i32 + +fn cond_get_id<'mir, 'tcx: 'mir>( + ecx: &MiriInterpCx<'mir, 'tcx>, + cond_op: &OpTy<'tcx, Provenance>, +) -> InterpResult<'tcx, Scalar> { + ecx.read_scalar_at_offset_atomic(cond_op, 4, ecx.machine.layouts.u32, AtomicReadOrd::Relaxed) +} + +fn cond_set_id<'mir, 'tcx: 'mir>( + ecx: &mut MiriInterpCx<'mir, 'tcx>, + cond_op: &OpTy<'tcx, Provenance>, + id: impl Into>, +) -> InterpResult<'tcx, ()> { + ecx.write_scalar_at_offset_atomic( + cond_op, + 4, + id, + layout_of_maybe_uninit(ecx.tcx, ecx.tcx.types.u32), + AtomicWriteOrd::Relaxed, + ) +} + +fn cond_get_or_create_id<'mir, 'tcx: 'mir>( + ecx: &mut MiriInterpCx<'mir, 'tcx>, + cond_op: &OpTy<'tcx, Provenance>, +) -> InterpResult<'tcx, CondvarId> { + let value_place = ecx.deref_operand_and_offset(cond_op, 4, ecx.machine.layouts.u32)?; + + ecx.condvar_get_or_create(|ecx, next_id| { + let (old, success) = ecx + .atomic_compare_exchange_scalar( + &value_place, + &ImmTy::from_uint(0u32, ecx.machine.layouts.u32), + next_id.to_u32_scalar(), + AtomicRwOrd::Relaxed, + AtomicReadOrd::Relaxed, + false, + )? + .to_scalar_pair(); + + Ok(if success.to_bool().expect("compare_exchange's second return value is a bool") { + // Caller of the closure needs to allocate next_id + None + } else { + Some(CondvarId::from_u32(old.to_u32().expect("layout is u32"))) + }) + }) +} + +fn cond_get_clock_id<'mir, 'tcx: 'mir>( + ecx: &MiriInterpCx<'mir, 'tcx>, + cond_op: &OpTy<'tcx, Provenance>, +) -> InterpResult<'tcx, Scalar> { + ecx.read_scalar_at_offset(cond_op, 8, ecx.machine.layouts.i32) +} + +fn cond_set_clock_id<'mir, 'tcx: 'mir>( + ecx: &mut MiriInterpCx<'mir, 'tcx>, + cond_op: &OpTy<'tcx, Provenance>, + clock_id: impl Into>, +) -> InterpResult<'tcx, ()> { + ecx.write_scalar_at_offset( + cond_op, + 8, + clock_id, + layout_of_maybe_uninit(ecx.tcx, ecx.tcx.types.i32), + ) +} + +/// Try to reacquire the mutex associated with the condition variable after we +/// were signaled. +fn reacquire_cond_mutex<'mir, 'tcx: 'mir>( + ecx: &mut MiriInterpCx<'mir, 'tcx>, + thread: ThreadId, + mutex: MutexId, +) -> InterpResult<'tcx> { + ecx.unblock_thread(thread); + if ecx.mutex_is_locked(mutex) { + ecx.mutex_enqueue_and_block(mutex, thread); + } else { + ecx.mutex_lock(mutex, thread); + } + Ok(()) +} + +/// After a thread waiting on a condvar was signalled: +/// Reacquire the conditional variable and remove the timeout callback if any +/// was registered. +fn post_cond_signal<'mir, 'tcx: 'mir>( + ecx: &mut MiriInterpCx<'mir, 'tcx>, + thread: ThreadId, + mutex: MutexId, +) -> InterpResult<'tcx> { + reacquire_cond_mutex(ecx, thread, mutex)?; + // Waiting for the mutex is not included in the waiting time because we need + // to acquire the mutex always even if we get a timeout. + ecx.unregister_timeout_callback_if_exists(thread); + Ok(()) +} + +/// Release the mutex associated with the condition variable because we are +/// entering the waiting state. +fn release_cond_mutex_and_block<'mir, 'tcx: 'mir>( + ecx: &mut MiriInterpCx<'mir, 'tcx>, + active_thread: ThreadId, + mutex: MutexId, +) -> InterpResult<'tcx> { + if let Some(old_locked_count) = ecx.mutex_unlock(mutex, active_thread) { + if old_locked_count != 1 { + throw_unsup_format!("awaiting on a lock acquired multiple times is not supported"); + } + } else { + throw_ub_format!("awaiting on unlocked or owned by a different thread mutex"); + } + ecx.block_thread(active_thread); + Ok(()) +} + +impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + fn pthread_mutexattr_init( + &mut self, + attr_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + let default_kind = this.eval_libc("PTHREAD_MUTEX_DEFAULT")?; + mutexattr_set_kind(this, attr_op, default_kind)?; + + Ok(0) + } + + fn pthread_mutexattr_settype( + &mut self, + attr_op: &OpTy<'tcx, Provenance>, + kind_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + let kind = this.read_scalar(kind_op)?; + if kind == this.eval_libc("PTHREAD_MUTEX_NORMAL")? { + // In `glibc` implementation, the numeric values of + // `PTHREAD_MUTEX_NORMAL` and `PTHREAD_MUTEX_DEFAULT` are equal. + // However, a mutex created by explicitly passing + // `PTHREAD_MUTEX_NORMAL` type has in some cases different behaviour + // from the default mutex for which the type was not explicitly + // specified. For a more detailed discussion, please see + // https://github.com/rust-lang/miri/issues/1419. + // + // To distinguish these two cases in already constructed mutexes, we + // use the same trick as glibc: for the case when + // `pthread_mutexattr_settype` is caled explicitly, we set the + // `PTHREAD_MUTEX_NORMAL_FLAG` flag. + let normal_kind = kind.to_i32()? | PTHREAD_MUTEX_NORMAL_FLAG; + // Check that after setting the flag, the kind is distinguishable + // from all other kinds. + assert_ne!(normal_kind, this.eval_libc("PTHREAD_MUTEX_DEFAULT")?.to_i32()?); + assert_ne!(normal_kind, this.eval_libc("PTHREAD_MUTEX_ERRORCHECK")?.to_i32()?); + assert_ne!(normal_kind, this.eval_libc("PTHREAD_MUTEX_RECURSIVE")?.to_i32()?); + mutexattr_set_kind(this, attr_op, Scalar::from_i32(normal_kind))?; + } else if kind == this.eval_libc("PTHREAD_MUTEX_DEFAULT")? + || kind == this.eval_libc("PTHREAD_MUTEX_ERRORCHECK")? + || kind == this.eval_libc("PTHREAD_MUTEX_RECURSIVE")? + { + mutexattr_set_kind(this, attr_op, kind)?; + } else { + let einval = this.eval_libc_i32("EINVAL")?; + return Ok(einval); + } + + Ok(0) + } + + fn pthread_mutexattr_destroy( + &mut self, + attr_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + // Destroying an uninit pthread_mutexattr is UB, so check to make sure it's not uninit. + mutexattr_get_kind(this, attr_op)?; + + // To catch double-destroys, we de-initialize the mutexattr. + // This is technically not right and might lead to false positives. For example, the below + // code is *likely* sound, even assuming uninit numbers are UB, but Miri complains. + // + // let mut x: MaybeUninit = MaybeUninit::zeroed(); + // libc::pthread_mutexattr_init(x.as_mut_ptr()); + // libc::pthread_mutexattr_destroy(x.as_mut_ptr()); + // x.assume_init(); + // + // However, the way libstd uses the pthread APIs works in our favor here, so we can get away with this. + // This can always be revisited to have some external state to catch double-destroys + // but not complain about the above code. See https://github.com/rust-lang/miri/pull/1933 + this.write_uninit(&this.deref_operand(attr_op)?.into())?; + + Ok(0) + } + + fn pthread_mutex_init( + &mut self, + mutex_op: &OpTy<'tcx, Provenance>, + attr_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + let attr = this.read_pointer(attr_op)?; + let kind = if this.ptr_is_null(attr)? { + this.eval_libc("PTHREAD_MUTEX_DEFAULT")? + } else { + mutexattr_get_kind(this, attr_op)? + }; + + // Write 0 to use the same code path as the static initializers. + mutex_set_id(this, mutex_op, Scalar::from_i32(0))?; + + mutex_set_kind(this, mutex_op, kind)?; + + Ok(0) + } + + fn pthread_mutex_lock(&mut self, mutex_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + let kind = mutex_get_kind(this, mutex_op)?; + let id = mutex_get_or_create_id(this, mutex_op)?; + let active_thread = this.get_active_thread(); + + if this.mutex_is_locked(id) { + let owner_thread = this.mutex_get_owner(id); + if owner_thread != active_thread { + // Enqueue the active thread. + this.mutex_enqueue_and_block(id, active_thread); + Ok(0) + } else { + // Trying to acquire the same mutex again. + if is_mutex_kind_default(this, kind)? { + throw_ub_format!("trying to acquire already locked default mutex"); + } else if is_mutex_kind_normal(this, kind)? { + throw_machine_stop!(TerminationInfo::Deadlock); + } else if kind == this.eval_libc("PTHREAD_MUTEX_ERRORCHECK")? { + this.eval_libc_i32("EDEADLK") + } else if kind == this.eval_libc("PTHREAD_MUTEX_RECURSIVE")? { + this.mutex_lock(id, active_thread); + Ok(0) + } else { + throw_unsup_format!( + "called pthread_mutex_lock on an unsupported type of mutex" + ); + } + } + } else { + // The mutex is unlocked. Let's lock it. + this.mutex_lock(id, active_thread); + Ok(0) + } + } + + fn pthread_mutex_trylock( + &mut self, + mutex_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + let kind = mutex_get_kind(this, mutex_op)?; + let id = mutex_get_or_create_id(this, mutex_op)?; + let active_thread = this.get_active_thread(); + + if this.mutex_is_locked(id) { + let owner_thread = this.mutex_get_owner(id); + if owner_thread != active_thread { + this.eval_libc_i32("EBUSY") + } else { + if is_mutex_kind_default(this, kind)? + || is_mutex_kind_normal(this, kind)? + || kind == this.eval_libc("PTHREAD_MUTEX_ERRORCHECK")? + { + this.eval_libc_i32("EBUSY") + } else if kind == this.eval_libc("PTHREAD_MUTEX_RECURSIVE")? { + this.mutex_lock(id, active_thread); + Ok(0) + } else { + throw_unsup_format!( + "called pthread_mutex_trylock on an unsupported type of mutex" + ); + } + } + } else { + // The mutex is unlocked. Let's lock it. + this.mutex_lock(id, active_thread); + Ok(0) + } + } + + fn pthread_mutex_unlock( + &mut self, + mutex_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + let kind = mutex_get_kind(this, mutex_op)?; + let id = mutex_get_or_create_id(this, mutex_op)?; + let active_thread = this.get_active_thread(); + + if let Some(_old_locked_count) = this.mutex_unlock(id, active_thread) { + // The mutex was locked by the current thread. + Ok(0) + } else { + // The mutex was locked by another thread or not locked at all. See + // the “Unlock When Not Owner” column in + // https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_mutex_unlock.html. + if is_mutex_kind_default(this, kind)? { + throw_ub_format!( + "unlocked a default mutex that was not locked by the current thread" + ); + } else if is_mutex_kind_normal(this, kind)? { + throw_ub_format!( + "unlocked a PTHREAD_MUTEX_NORMAL mutex that was not locked by the current thread" + ); + } else if kind == this.eval_libc("PTHREAD_MUTEX_ERRORCHECK")? + || kind == this.eval_libc("PTHREAD_MUTEX_RECURSIVE")? + { + this.eval_libc_i32("EPERM") + } else { + throw_unsup_format!("called pthread_mutex_unlock on an unsupported type of mutex"); + } + } + } + + fn pthread_mutex_destroy( + &mut self, + mutex_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + let id = mutex_get_or_create_id(this, mutex_op)?; + + if this.mutex_is_locked(id) { + throw_ub_format!("destroyed a locked mutex"); + } + + // Destroying an uninit pthread_mutex is UB, so check to make sure it's not uninit. + mutex_get_kind(this, mutex_op)?; + mutex_get_id(this, mutex_op)?; + + // This might lead to false positives, see comment in pthread_mutexattr_destroy + this.write_uninit(&this.deref_operand(mutex_op)?.into())?; + // FIXME: delete interpreter state associated with this mutex. + + Ok(0) + } + + fn pthread_rwlock_rdlock( + &mut self, + rwlock_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + let id = rwlock_get_or_create_id(this, rwlock_op)?; + let active_thread = this.get_active_thread(); + + if this.rwlock_is_write_locked(id) { + this.rwlock_enqueue_and_block_reader(id, active_thread); + Ok(0) + } else { + this.rwlock_reader_lock(id, active_thread); + Ok(0) + } + } + + fn pthread_rwlock_tryrdlock( + &mut self, + rwlock_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + let id = rwlock_get_or_create_id(this, rwlock_op)?; + let active_thread = this.get_active_thread(); + + if this.rwlock_is_write_locked(id) { + this.eval_libc_i32("EBUSY") + } else { + this.rwlock_reader_lock(id, active_thread); + Ok(0) + } + } + + fn pthread_rwlock_wrlock( + &mut self, + rwlock_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + let id = rwlock_get_or_create_id(this, rwlock_op)?; + let active_thread = this.get_active_thread(); + + if this.rwlock_is_locked(id) { + // Note: this will deadlock if the lock is already locked by this + // thread in any way. + // + // Relevant documentation: + // https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_rwlock_wrlock.html + // An in-depth discussion on this topic: + // https://github.com/rust-lang/rust/issues/53127 + // + // FIXME: Detect and report the deadlock proactively. (We currently + // report the deadlock only when no thread can continue execution, + // but we could detect that this lock is already locked and report + // an error.) + this.rwlock_enqueue_and_block_writer(id, active_thread); + } else { + this.rwlock_writer_lock(id, active_thread); + } + + Ok(0) + } + + fn pthread_rwlock_trywrlock( + &mut self, + rwlock_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + let id = rwlock_get_or_create_id(this, rwlock_op)?; + let active_thread = this.get_active_thread(); + + if this.rwlock_is_locked(id) { + this.eval_libc_i32("EBUSY") + } else { + this.rwlock_writer_lock(id, active_thread); + Ok(0) + } + } + + fn pthread_rwlock_unlock( + &mut self, + rwlock_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + let id = rwlock_get_or_create_id(this, rwlock_op)?; + let active_thread = this.get_active_thread(); + + #[allow(clippy::if_same_then_else)] + if this.rwlock_reader_unlock(id, active_thread) { + Ok(0) + } else if this.rwlock_writer_unlock(id, active_thread) { + Ok(0) + } else { + throw_ub_format!("unlocked an rwlock that was not locked by the active thread"); + } + } + + fn pthread_rwlock_destroy( + &mut self, + rwlock_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + let id = rwlock_get_or_create_id(this, rwlock_op)?; + + if this.rwlock_is_locked(id) { + throw_ub_format!("destroyed a locked rwlock"); + } + + // Destroying an uninit pthread_rwlock is UB, so check to make sure it's not uninit. + rwlock_get_id(this, rwlock_op)?; + + // This might lead to false positives, see comment in pthread_mutexattr_destroy + this.write_uninit(&this.deref_operand(rwlock_op)?.into())?; + // FIXME: delete interpreter state associated with this rwlock. + + Ok(0) + } + + fn pthread_condattr_init( + &mut self, + attr_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + // The default value of the clock attribute shall refer to the system + // clock. + // https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_condattr_setclock.html + let default_clock_id = this.eval_libc("CLOCK_REALTIME")?; + condattr_set_clock_id(this, attr_op, default_clock_id)?; + + Ok(0) + } + + fn pthread_condattr_setclock( + &mut self, + attr_op: &OpTy<'tcx, Provenance>, + clock_id_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + + let clock_id = this.read_scalar(clock_id_op)?; + if clock_id == this.eval_libc("CLOCK_REALTIME")? + || clock_id == this.eval_libc("CLOCK_MONOTONIC")? + { + condattr_set_clock_id(this, attr_op, clock_id)?; + } else { + let einval = this.eval_libc_i32("EINVAL")?; + return Ok(Scalar::from_i32(einval)); + } + + Ok(Scalar::from_i32(0)) + } + + fn pthread_condattr_getclock( + &mut self, + attr_op: &OpTy<'tcx, Provenance>, + clk_id_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + + let clock_id = condattr_get_clock_id(this, attr_op)?; + this.write_scalar(clock_id, &this.deref_operand(clk_id_op)?.into())?; + + Ok(Scalar::from_i32(0)) + } + + fn pthread_condattr_destroy( + &mut self, + attr_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + // Destroying an uninit pthread_condattr is UB, so check to make sure it's not uninit. + condattr_get_clock_id(this, attr_op)?; + + // This might lead to false positives, see comment in pthread_mutexattr_destroy + this.write_uninit(&this.deref_operand(attr_op)?.into())?; + + Ok(0) + } + + fn pthread_cond_init( + &mut self, + cond_op: &OpTy<'tcx, Provenance>, + attr_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + let attr = this.read_pointer(attr_op)?; + let clock_id = if this.ptr_is_null(attr)? { + this.eval_libc("CLOCK_REALTIME")? + } else { + condattr_get_clock_id(this, attr_op)? + }; + + // Write 0 to use the same code path as the static initializers. + cond_set_id(this, cond_op, Scalar::from_i32(0))?; + + cond_set_clock_id(this, cond_op, clock_id)?; + + Ok(0) + } + + fn pthread_cond_signal(&mut self, cond_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + let id = cond_get_or_create_id(this, cond_op)?; + if let Some((thread, mutex)) = this.condvar_signal(id) { + post_cond_signal(this, thread, mutex)?; + } + + Ok(0) + } + + fn pthread_cond_broadcast( + &mut self, + cond_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + let id = cond_get_or_create_id(this, cond_op)?; + + while let Some((thread, mutex)) = this.condvar_signal(id) { + post_cond_signal(this, thread, mutex)?; + } + + Ok(0) + } + + fn pthread_cond_wait( + &mut self, + cond_op: &OpTy<'tcx, Provenance>, + mutex_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + let id = cond_get_or_create_id(this, cond_op)?; + let mutex_id = mutex_get_or_create_id(this, mutex_op)?; + let active_thread = this.get_active_thread(); + + release_cond_mutex_and_block(this, active_thread, mutex_id)?; + this.condvar_wait(id, active_thread, mutex_id); + + Ok(0) + } + + fn pthread_cond_timedwait( + &mut self, + cond_op: &OpTy<'tcx, Provenance>, + mutex_op: &OpTy<'tcx, Provenance>, + abstime_op: &OpTy<'tcx, Provenance>, + dest: &PlaceTy<'tcx, Provenance>, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + + this.check_no_isolation("`pthread_cond_timedwait`")?; + + let id = cond_get_or_create_id(this, cond_op)?; + let mutex_id = mutex_get_or_create_id(this, mutex_op)?; + let active_thread = this.get_active_thread(); + + // Extract the timeout. + let clock_id = cond_get_clock_id(this, cond_op)?.to_i32()?; + let duration = match this.read_timespec(&this.deref_operand(abstime_op)?)? { + Some(duration) => duration, + None => { + let einval = this.eval_libc("EINVAL")?; + this.write_scalar(einval, dest)?; + return Ok(()); + } + }; + + let timeout_time = if clock_id == this.eval_libc_i32("CLOCK_REALTIME")? { + Time::RealTime(SystemTime::UNIX_EPOCH.checked_add(duration).unwrap()) + } else if clock_id == this.eval_libc_i32("CLOCK_MONOTONIC")? { + Time::Monotonic(this.machine.clock.anchor().checked_add(duration).unwrap()) + } else { + throw_unsup_format!("unsupported clock id: {}", clock_id); + }; + + release_cond_mutex_and_block(this, active_thread, mutex_id)?; + this.condvar_wait(id, active_thread, mutex_id); + + // We return success for now and override it in the timeout callback. + this.write_scalar(Scalar::from_i32(0), dest)?; + + // Register the timeout callback. + let dest = dest.clone(); + this.register_timeout_callback( + active_thread, + timeout_time, + Box::new(move |ecx| { + // We are not waiting for the condvar any more, wait for the + // mutex instead. + reacquire_cond_mutex(ecx, active_thread, mutex_id)?; + + // Remove the thread from the conditional variable. + ecx.condvar_remove_waiter(id, active_thread); + + // Set the return value: we timed out. + let etimedout = ecx.eval_libc("ETIMEDOUT")?; + ecx.write_scalar(etimedout, &dest)?; + + Ok(()) + }), + ); + + Ok(()) + } + + fn pthread_cond_destroy( + &mut self, + cond_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + let id = cond_get_or_create_id(this, cond_op)?; + if this.condvar_is_awaited(id) { + throw_ub_format!("destroying an awaited conditional variable"); + } + + // Destroying an uninit pthread_cond is UB, so check to make sure it's not uninit. + cond_get_id(this, cond_op)?; + cond_get_clock_id(this, cond_op)?; + + // This might lead to false positives, see comment in pthread_mutexattr_destroy + this.write_uninit(&this.deref_operand(cond_op)?.into())?; + // FIXME: delete interpreter state associated with this condvar. + + Ok(0) + } +} + +fn layout_of_maybe_uninit<'tcx>(tcx: TyCtxtAt<'tcx>, param: Ty<'tcx>) -> TyAndLayout<'tcx> { + let def_id = tcx.require_lang_item(LangItem::MaybeUninit, None); + let ty = tcx.bound_type_of(def_id).subst(*tcx, &[param.into()]); + + let param_env = tcx.param_env(def_id); + tcx.layout_of(param_env.and(ty)).unwrap() +} diff --git a/src/tools/miri/src/shims/unix/thread.rs b/src/tools/miri/src/shims/unix/thread.rs new file mode 100644 index 0000000000000..59474d8d10ad7 --- /dev/null +++ b/src/tools/miri/src/shims/unix/thread.rs @@ -0,0 +1,93 @@ +use crate::*; +use rustc_middle::ty::layout::LayoutOf; +use rustc_target::spec::abi::Abi; + +impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + fn pthread_create( + &mut self, + thread: &OpTy<'tcx, Provenance>, + _attr: &OpTy<'tcx, Provenance>, + start_routine: &OpTy<'tcx, Provenance>, + arg: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + let thread_info_place = this.deref_operand(thread)?; + + let start_routine = this.read_pointer(start_routine)?; + + let func_arg = this.read_immediate(arg)?; + + this.start_thread( + Some(thread_info_place), + start_routine, + Abi::C { unwind: false }, + func_arg, + this.layout_of(this.tcx.types.usize)?, + )?; + + Ok(0) + } + + fn pthread_join( + &mut self, + thread: &OpTy<'tcx, Provenance>, + retval: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + if !this.ptr_is_null(this.read_pointer(retval)?)? { + // FIXME: implement reading the thread function's return place. + throw_unsup_format!("Miri supports pthread_join only with retval==NULL"); + } + + let thread_id = this.read_scalar(thread)?.to_machine_usize(this)?; + this.join_thread_exclusive(thread_id.try_into().expect("thread ID should fit in u32"))?; + + Ok(0) + } + + fn pthread_detach(&mut self, thread: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + let thread_id = this.read_scalar(thread)?.to_machine_usize(this)?; + this.detach_thread( + thread_id.try_into().expect("thread ID should fit in u32"), + /*allow_terminated_joined*/ false, + )?; + + Ok(0) + } + + fn pthread_self(&mut self) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + + let thread_id = this.get_active_thread(); + Ok(Scalar::from_machine_usize(thread_id.into(), this)) + } + + fn pthread_setname_np( + &mut self, + thread: Scalar, + name: Scalar, + ) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + + let thread = ThreadId::try_from(thread.to_machine_usize(this)?).unwrap(); + let name = name.to_pointer(this)?; + + let name = this.read_c_str(name)?.to_owned(); + this.set_thread_name(thread, name); + + Ok(Scalar::from_u32(0)) + } + + fn sched_yield(&mut self) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + this.yield_active_thread(); + + Ok(0) + } +} diff --git a/src/tools/miri/src/shims/windows/dlsym.rs b/src/tools/miri/src/shims/windows/dlsym.rs new file mode 100644 index 0000000000000..41b9473f81fef --- /dev/null +++ b/src/tools/miri/src/shims/windows/dlsym.rs @@ -0,0 +1,136 @@ +use rustc_middle::mir; +use rustc_target::abi::Size; +use rustc_target::spec::abi::Abi; + +use log::trace; + +use crate::helpers::check_arg_count; +use crate::shims::windows::handle::{EvalContextExt as _, Handle, PseudoHandle}; +use crate::*; + +#[derive(Debug, Copy, Clone)] +pub enum Dlsym { + NtWriteFile, + SetThreadDescription, +} + +impl Dlsym { + // Returns an error for unsupported symbols, and None if this symbol + // should become a NULL pointer (pretend it does not exist). + pub fn from_str<'tcx>(name: &str) -> InterpResult<'tcx, Option> { + Ok(match name { + "GetSystemTimePreciseAsFileTime" => None, + "NtWriteFile" => Some(Dlsym::NtWriteFile), + "SetThreadDescription" => Some(Dlsym::SetThreadDescription), + _ => throw_unsup_format!("unsupported Windows dlsym: {}", name), + }) + } +} + +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + fn call_dlsym( + &mut self, + dlsym: Dlsym, + abi: Abi, + args: &[OpTy<'tcx, Provenance>], + dest: &PlaceTy<'tcx, Provenance>, + ret: Option, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + let ret = ret.expect("we don't support any diverging dlsym"); + assert!(this.tcx.sess.target.os == "windows"); + + this.check_abi(abi, Abi::System { unwind: false })?; + + match dlsym { + Dlsym::NtWriteFile => { + if !this.frame_in_std() { + throw_unsup_format!( + "`NtWriteFile` support is crude and just enough for stdout to work" + ); + } + + let [ + handle, + _event, + _apc_routine, + _apc_context, + io_status_block, + buf, + n, + byte_offset, + _key, + ] = check_arg_count(args)?; + let handle = this.read_scalar(handle)?.to_machine_isize(this)?; + let buf = this.read_pointer(buf)?; + let n = this.read_scalar(n)?.to_u32()?; + let byte_offset = this.read_scalar(byte_offset)?.to_machine_usize(this)?; // is actually a pointer + let io_status_block = this.deref_operand(io_status_block)?; + + if byte_offset != 0 { + throw_unsup_format!( + "`NtWriteFile` `ByteOffset` paremeter is non-null, which is unsupported" + ); + } + + let written = if handle == -11 || handle == -12 { + // stdout/stderr + use std::io::{self, Write}; + + let buf_cont = + this.read_bytes_ptr_strip_provenance(buf, Size::from_bytes(u64::from(n)))?; + let res = if this.machine.mute_stdout_stderr { + Ok(buf_cont.len()) + } else if handle == -11 { + io::stdout().write(buf_cont) + } else { + io::stderr().write(buf_cont) + }; + // We write at most `n` bytes, which is a `u32`, so we cannot have written more than that. + res.ok().map(|n| u32::try_from(n).unwrap()) + } else { + throw_unsup_format!( + "on Windows, writing to anything except stdout/stderr is not supported" + ) + }; + // We have to put the result into io_status_block. + if let Some(n) = written { + let io_status_information = + this.mplace_field_named(&io_status_block, "Information")?; + this.write_scalar( + Scalar::from_machine_usize(n.into(), this), + &io_status_information.into(), + )?; + } + // Return whether this was a success. >= 0 is success. + // For the error code we arbitrarily pick 0xC0000185, STATUS_IO_DEVICE_ERROR. + this.write_scalar( + Scalar::from_u32(if written.is_some() { 0 } else { 0xC0000185u32 }), + dest, + )?; + } + Dlsym::SetThreadDescription => { + let [handle, name] = check_arg_count(args)?; + + let handle = this.read_scalar(handle)?; + + let name = this.read_wide_str(this.read_pointer(name)?)?; + + let thread = match Handle::from_scalar(handle, this)? { + Some(Handle::Thread(thread)) => thread, + Some(Handle::Pseudo(PseudoHandle::CurrentThread)) => this.get_active_thread(), + _ => this.invalid_handle("SetThreadDescription")?, + }; + + this.set_thread_name_wide(thread, &name); + + this.write_null(dest)?; + } + } + + trace!("{:?}", this.dump_place(**dest)); + this.go_to_block(ret); + Ok(()) + } +} diff --git a/src/tools/miri/src/shims/windows/foreign_items.rs b/src/tools/miri/src/shims/windows/foreign_items.rs new file mode 100644 index 0000000000000..53ab97b255e56 --- /dev/null +++ b/src/tools/miri/src/shims/windows/foreign_items.rs @@ -0,0 +1,442 @@ +use std::iter; + +use rustc_span::Symbol; +use rustc_target::abi::Size; +use rustc_target::spec::abi::Abi; + +use crate::*; +use shims::foreign_items::EmulateByNameResult; +use shims::windows::handle::{EvalContextExt as _, Handle, PseudoHandle}; +use shims::windows::sync::EvalContextExt as _; +use shims::windows::thread::EvalContextExt as _; + +use smallvec::SmallVec; + +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_by_name( + &mut self, + link_name: Symbol, + abi: Abi, + args: &[OpTy<'tcx, Provenance>], + dest: &PlaceTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, EmulateByNameResult<'mir, 'tcx>> { + let this = self.eval_context_mut(); + + // See `fn emulate_foreign_item_by_name` in `shims/foreign_items.rs` for the general pattern. + + // Windows API stubs. + // HANDLE = isize + // NTSTATUS = LONH = i32 + // DWORD = ULONG = u32 + // BOOL = i32 + // BOOLEAN = u8 + match link_name.as_str() { + // Environment related shims + "GetEnvironmentVariableW" => { + let [name, buf, size] = + this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + let result = this.GetEnvironmentVariableW(name, buf, size)?; + this.write_scalar(Scalar::from_u32(result), dest)?; + } + "SetEnvironmentVariableW" => { + let [name, value] = + this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + let result = this.SetEnvironmentVariableW(name, value)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "GetEnvironmentStringsW" => { + let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + let result = this.GetEnvironmentStringsW()?; + this.write_pointer(result, dest)?; + } + "FreeEnvironmentStringsW" => { + let [env_block] = + this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + let result = this.FreeEnvironmentStringsW(env_block)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "GetCurrentDirectoryW" => { + let [size, buf] = + this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + let result = this.GetCurrentDirectoryW(size, buf)?; + this.write_scalar(Scalar::from_u32(result), dest)?; + } + "SetCurrentDirectoryW" => { + let [path] = + this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + let result = this.SetCurrentDirectoryW(path)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + + // Allocation + "HeapAlloc" => { + let [handle, flags, size] = + this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + this.read_scalar(handle)?.to_machine_isize(this)?; + let flags = this.read_scalar(flags)?.to_u32()?; + let size = this.read_scalar(size)?.to_machine_usize(this)?; + let zero_init = (flags & 0x00000008) != 0; // HEAP_ZERO_MEMORY + let res = this.malloc(size, zero_init, MiriMemoryKind::WinHeap)?; + this.write_pointer(res, dest)?; + } + "HeapFree" => { + let [handle, flags, ptr] = + this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + this.read_scalar(handle)?.to_machine_isize(this)?; + this.read_scalar(flags)?.to_u32()?; + let ptr = this.read_pointer(ptr)?; + this.free(ptr, MiriMemoryKind::WinHeap)?; + this.write_scalar(Scalar::from_i32(1), dest)?; + } + "HeapReAlloc" => { + let [handle, flags, ptr, size] = + this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + this.read_scalar(handle)?.to_machine_isize(this)?; + this.read_scalar(flags)?.to_u32()?; + let ptr = this.read_pointer(ptr)?; + let size = this.read_scalar(size)?.to_machine_usize(this)?; + let res = this.realloc(ptr, size, MiriMemoryKind::WinHeap)?; + this.write_pointer(res, dest)?; + } + + // errno + "SetLastError" => { + let [error] = + this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + let error = this.read_scalar(error)?; + this.set_last_error(error)?; + } + "GetLastError" => { + let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + let last_error = this.get_last_error()?; + this.write_scalar(last_error, dest)?; + } + + // Querying system information + "GetSystemInfo" => { + // Also called from `page_size` crate. + let [system_info] = + this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + let system_info = this.deref_operand(system_info)?; + // Initialize with `0`. + this.write_bytes_ptr( + system_info.ptr, + iter::repeat(0u8).take(system_info.layout.size.bytes_usize()), + )?; + // Set selected fields. + let word_layout = this.machine.layouts.u16; + let dword_layout = this.machine.layouts.u32; + let usize_layout = this.machine.layouts.usize; + + // Using `mplace_field` is error-prone, see: https://github.com/rust-lang/miri/issues/2136. + // Pointer fields have different sizes on different targets. + // To avoid all these issue we calculate the offsets ourselves. + let field_sizes = [ + word_layout.size, // 0, wProcessorArchitecture : WORD + word_layout.size, // 1, wReserved : WORD + dword_layout.size, // 2, dwPageSize : DWORD + usize_layout.size, // 3, lpMinimumApplicationAddress : LPVOID + usize_layout.size, // 4, lpMaximumApplicationAddress : LPVOID + usize_layout.size, // 5, dwActiveProcessorMask : DWORD_PTR + dword_layout.size, // 6, dwNumberOfProcessors : DWORD + dword_layout.size, // 7, dwProcessorType : DWORD + dword_layout.size, // 8, dwAllocationGranularity : DWORD + word_layout.size, // 9, wProcessorLevel : WORD + word_layout.size, // 10, wProcessorRevision : WORD + ]; + let field_offsets: SmallVec<[Size; 11]> = field_sizes + .iter() + .copied() + .scan(Size::ZERO, |a, x| { + let res = Some(*a); + *a += x; + res + }) + .collect(); + + // Set page size. + let page_size = system_info.offset(field_offsets[2], dword_layout, &this.tcx)?; + this.write_scalar( + Scalar::from_int(PAGE_SIZE, dword_layout.size), + &page_size.into(), + )?; + // Set number of processors. + let num_cpus = system_info.offset(field_offsets[6], dword_layout, &this.tcx)?; + this.write_scalar(Scalar::from_int(NUM_CPUS, dword_layout.size), &num_cpus.into())?; + } + + // Thread-local storage + "TlsAlloc" => { + // This just creates a key; Windows does not natively support TLS destructors. + + // Create key and return it. + let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + let key = this.machine.tls.create_tls_key(None, dest.layout.size)?; + this.write_scalar(Scalar::from_uint(key, dest.layout.size), dest)?; + } + "TlsGetValue" => { + let [key] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + let key = u128::from(this.read_scalar(key)?.to_u32()?); + let active_thread = this.get_active_thread(); + let ptr = this.machine.tls.load_tls(key, active_thread, this)?; + this.write_scalar(ptr, dest)?; + } + "TlsSetValue" => { + let [key, new_ptr] = + this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + let key = u128::from(this.read_scalar(key)?.to_u32()?); + let active_thread = this.get_active_thread(); + let new_data = this.read_scalar(new_ptr)?; + this.machine.tls.store_tls(key, active_thread, new_data, &*this.tcx)?; + + // Return success (`1`). + this.write_scalar(Scalar::from_i32(1), dest)?; + } + + // Access to command-line arguments + "GetCommandLineW" => { + let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + this.write_pointer( + this.machine.cmd_line.expect("machine must be initialized").ptr, + dest, + )?; + } + + // Time related shims + "GetSystemTimeAsFileTime" => { + #[allow(non_snake_case)] + let [LPFILETIME] = + this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + this.GetSystemTimeAsFileTime(LPFILETIME)?; + } + "QueryPerformanceCounter" => { + #[allow(non_snake_case)] + let [lpPerformanceCount] = + this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + let result = this.QueryPerformanceCounter(lpPerformanceCount)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "QueryPerformanceFrequency" => { + #[allow(non_snake_case)] + let [lpFrequency] = + this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + let result = this.QueryPerformanceFrequency(lpFrequency)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "Sleep" => { + let [timeout] = + this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + + this.Sleep(timeout)?; + } + + // Synchronization primitives + "AcquireSRWLockExclusive" => { + let [ptr] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + this.AcquireSRWLockExclusive(ptr)?; + } + "ReleaseSRWLockExclusive" => { + let [ptr] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + this.ReleaseSRWLockExclusive(ptr)?; + } + "TryAcquireSRWLockExclusive" => { + let [ptr] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + let ret = this.TryAcquireSRWLockExclusive(ptr)?; + this.write_scalar(Scalar::from_u8(ret), dest)?; + } + "AcquireSRWLockShared" => { + let [ptr] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + this.AcquireSRWLockShared(ptr)?; + } + "ReleaseSRWLockShared" => { + let [ptr] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + this.ReleaseSRWLockShared(ptr)?; + } + "TryAcquireSRWLockShared" => { + let [ptr] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + let ret = this.TryAcquireSRWLockShared(ptr)?; + this.write_scalar(Scalar::from_u8(ret), dest)?; + } + + // Dynamic symbol loading + "GetProcAddress" => { + #[allow(non_snake_case)] + let [hModule, lpProcName] = + this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + this.read_scalar(hModule)?.to_machine_isize(this)?; + let name = this.read_c_str(this.read_pointer(lpProcName)?)?; + if let Some(dlsym) = Dlsym::from_str(name, &this.tcx.sess.target.os)? { + let ptr = this.create_fn_alloc_ptr(FnVal::Other(dlsym)); + this.write_pointer(ptr, dest)?; + } else { + this.write_null(dest)?; + } + } + + // Miscellaneous + "SystemFunction036" => { + // This is really 'RtlGenRandom'. + let [ptr, len] = + this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + let ptr = this.read_pointer(ptr)?; + let len = this.read_scalar(len)?.to_u32()?; + this.gen_random(ptr, len.into())?; + this.write_scalar(Scalar::from_bool(true), dest)?; + } + "BCryptGenRandom" => { + let [algorithm, ptr, len, flags] = + this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + let algorithm = this.read_scalar(algorithm)?; + let algorithm = algorithm.to_machine_usize(this)?; + let ptr = this.read_pointer(ptr)?; + let len = this.read_scalar(len)?.to_u32()?; + let flags = this.read_scalar(flags)?.to_u32()?; + match flags { + 0 => { + if algorithm != 0x81 { + // BCRYPT_RNG_ALG_HANDLE + throw_unsup_format!( + "BCryptGenRandom algorithm must be BCRYPT_RNG_ALG_HANDLE when the flag is 0" + ); + } + } + 2 => { + // BCRYPT_USE_SYSTEM_PREFERRED_RNG + if algorithm != 0 { + throw_unsup_format!( + "BCryptGenRandom algorithm must be NULL when the flag is BCRYPT_USE_SYSTEM_PREFERRED_RNG" + ); + } + } + _ => { + throw_unsup_format!( + "BCryptGenRandom is only supported with BCRYPT_USE_SYSTEM_PREFERRED_RNG or BCRYPT_RNG_ALG_HANDLE" + ); + } + } + this.gen_random(ptr, len.into())?; + this.write_null(dest)?; // STATUS_SUCCESS + } + "GetConsoleScreenBufferInfo" => { + // `term` needs this, so we fake it. + let [console, buffer_info] = + this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + this.read_scalar(console)?.to_machine_isize(this)?; + this.deref_operand(buffer_info)?; + // Indicate an error. + // FIXME: we should set last_error, but to what? + this.write_null(dest)?; + } + "GetConsoleMode" => { + // Windows "isatty" (in libtest) needs this, so we fake it. + let [console, mode] = + this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + this.read_scalar(console)?.to_machine_isize(this)?; + this.deref_operand(mode)?; + // Indicate an error. + // FIXME: we should set last_error, but to what? + this.write_null(dest)?; + } + "GetStdHandle" => { + let [which] = + this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + let which = this.read_scalar(which)?.to_i32()?; + // We just make this the identity function, so we know later in `NtWriteFile` which + // one it is. This is very fake, but libtest needs it so we cannot make it a + // std-only shim. + // FIXME: this should return real HANDLEs when io support is added + this.write_scalar(Scalar::from_machine_isize(which.into(), this), dest)?; + } + "CloseHandle" => { + let [handle] = + this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + + this.CloseHandle(handle)?; + + this.write_scalar(Scalar::from_u32(1), dest)?; + } + + // Threading + "CreateThread" => { + let [security, stacksize, start, arg, flags, thread] = + this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + + let thread_id = + this.CreateThread(security, stacksize, start, arg, flags, thread)?; + + this.write_scalar(Handle::Thread(thread_id).to_scalar(this), dest)?; + } + "WaitForSingleObject" => { + let [handle, timeout] = + this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + + let ret = this.WaitForSingleObject(handle, timeout)?; + this.write_scalar(Scalar::from_u32(ret), dest)?; + } + "GetCurrentThread" => { + let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + + this.write_scalar( + Handle::Pseudo(PseudoHandle::CurrentThread).to_scalar(this), + 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. + "GetProcessHeap" if this.frame_in_std() => { + let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + // Just fake a HANDLE + // It's fine to not use the Handle type here because its a stub + this.write_scalar(Scalar::from_machine_isize(1, this), dest)?; + } + "GetModuleHandleA" if this.frame_in_std() => { + #[allow(non_snake_case)] + let [_lpModuleName] = + this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + // We need to return something non-null here to make `compat_fn!` work. + this.write_scalar(Scalar::from_machine_isize(1, this), dest)?; + } + "SetConsoleTextAttribute" if this.frame_in_std() => { + #[allow(non_snake_case)] + let [_hConsoleOutput, _wAttribute] = + this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + // Pretend these does not exist / nothing happened, by returning zero. + this.write_null(dest)?; + } + "AddVectoredExceptionHandler" if this.frame_in_std() => { + #[allow(non_snake_case)] + let [_First, _Handler] = + this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + // Any non zero value works for the stdlib. This is just used for stack overflows anyway. + this.write_scalar(Scalar::from_machine_usize(1, this), dest)?; + } + "SetThreadStackGuarantee" if this.frame_in_std() => { + #[allow(non_snake_case)] + let [_StackSizeInBytes] = + this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + // Any non zero value works for the stdlib. This is just used for stack overflows anyway. + this.write_scalar(Scalar::from_u32(1), dest)?; + } + "GetCurrentProcessId" if this.frame_in_std() => { + let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + let result = this.GetCurrentProcessId()?; + this.write_scalar(Scalar::from_u32(result), dest)?; + } + // this is only callable from std because we know that std ignores the return value + "SwitchToThread" if this.frame_in_std() => { + let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + + this.yield_active_thread(); + + // FIXME: this should return a nonzero value if this call does result in switching to another thread. + this.write_null(dest)?; + } + + _ => return Ok(EmulateByNameResult::NotSupported), + } + + Ok(EmulateByNameResult::NeedsJumping) + } +} diff --git a/src/tools/miri/src/shims/windows/handle.rs b/src/tools/miri/src/shims/windows/handle.rs new file mode 100644 index 0000000000000..5b22c4bd73584 --- /dev/null +++ b/src/tools/miri/src/shims/windows/handle.rs @@ -0,0 +1,171 @@ +use rustc_target::abi::HasDataLayout; +use std::mem::variant_count; + +use crate::*; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum PseudoHandle { + CurrentThread, +} + +/// Miri representation of a Windows `HANDLE` +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum Handle { + Null, + Pseudo(PseudoHandle), + Thread(ThreadId), +} + +impl PseudoHandle { + const CURRENT_THREAD_VALUE: u32 = 0; + + fn value(self) -> u32 { + match self { + Self::CurrentThread => Self::CURRENT_THREAD_VALUE, + } + } + + fn from_value(value: u32) -> Option { + match value { + Self::CURRENT_THREAD_VALUE => Some(Self::CurrentThread), + _ => None, + } + } +} + +impl Handle { + const NULL_DISCRIMINANT: u32 = 0; + const PSEUDO_DISCRIMINANT: u32 = 1; + const THREAD_DISCRIMINANT: u32 = 2; + + fn discriminant(self) -> u32 { + match self { + Self::Null => Self::NULL_DISCRIMINANT, + Self::Pseudo(_) => Self::PSEUDO_DISCRIMINANT, + Self::Thread(_) => Self::THREAD_DISCRIMINANT, + } + } + + fn data(self) -> u32 { + match self { + Self::Null => 0, + Self::Pseudo(pseudo_handle) => pseudo_handle.value(), + Self::Thread(thread) => thread.to_u32(), + } + } + + fn packed_disc_size() -> u32 { + // ceil(log2(x)) is how many bits it takes to store x numbers + let variant_count = variant_count::(); + + // however, std's ilog2 is floor(log2(x)) + let floor_log2 = variant_count.ilog2(); + + // we need to add one for non powers of two to compensate for the difference + #[allow(clippy::integer_arithmetic)] // cannot overflow + if variant_count.is_power_of_two() { floor_log2 } else { floor_log2 + 1 } + } + + /// Converts a handle into its machine representation. + /// + /// The upper [`Self::packed_disc_size()`] bits are used to store a discriminant corresponding to the handle variant. + /// The remaining bits are used for the variant's field. + /// + /// None of this layout is guaranteed to applications by Windows or Miri. + fn to_packed(self) -> u32 { + let disc_size = Self::packed_disc_size(); + let data_size = u32::BITS.checked_sub(disc_size).unwrap(); + + let discriminant = self.discriminant(); + let data = self.data(); + + // make sure the discriminant fits into `disc_size` bits + assert!(discriminant < 2u32.pow(disc_size)); + + // make sure the data fits into `data_size` bits + assert!(data < 2u32.pow(data_size)); + + // packs the data into the lower `data_size` bits + // and packs the discriminant right above the data + #[allow(clippy::integer_arithmetic)] // cannot overflow + return discriminant << data_size | data; + } + + fn new(discriminant: u32, data: u32) -> Option { + match discriminant { + Self::NULL_DISCRIMINANT if data == 0 => Some(Self::Null), + Self::PSEUDO_DISCRIMINANT => Some(Self::Pseudo(PseudoHandle::from_value(data)?)), + Self::THREAD_DISCRIMINANT => Some(Self::Thread(data.into())), + _ => None, + } + } + + /// see docs for `to_packed` + fn from_packed(handle: u32) -> Option { + let disc_size = Self::packed_disc_size(); + let data_size = u32::BITS.checked_sub(disc_size).unwrap(); + + // the lower `data_size` bits of this mask are 1 + #[allow(clippy::integer_arithmetic)] // cannot overflow + let data_mask = 2u32.pow(data_size) - 1; + + // the discriminant is stored right above the lower `data_size` bits + #[allow(clippy::integer_arithmetic)] // cannot overflow + let discriminant = handle >> data_size; + + // the data is stored in the lower `data_size` bits + let data = handle & data_mask; + + Self::new(discriminant, data) + } + + pub fn to_scalar(self, cx: &impl HasDataLayout) -> Scalar { + // 64-bit handles are sign extended 32-bit handles + // see https://docs.microsoft.com/en-us/windows/win32/winprog64/interprocess-communication + #[allow(clippy::cast_possible_wrap)] // we want it to wrap + let signed_handle = self.to_packed() as i32; + Scalar::from_machine_isize(signed_handle.into(), cx) + } + + pub fn from_scalar<'tcx>( + handle: Scalar, + cx: &impl HasDataLayout, + ) -> InterpResult<'tcx, Option> { + let sign_extended_handle = handle.to_machine_isize(cx)?; + + #[allow(clippy::cast_sign_loss)] // we want to lose the sign + let handle = if let Ok(signed_handle) = i32::try_from(sign_extended_handle) { + signed_handle as u32 + } else { + // if a handle doesn't fit in an i32, it isn't valid. + return Ok(None); + }; + + Ok(Self::from_packed(handle)) + } +} + +impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} + +#[allow(non_snake_case)] +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + fn invalid_handle(&mut self, function_name: &str) -> InterpResult<'tcx, !> { + throw_machine_stop!(TerminationInfo::Abort(format!( + "invalid handle passed to `{function_name}`" + ))) + } + + fn CloseHandle(&mut self, handle_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + + let handle = this.read_scalar(handle_op)?; + + match Handle::from_scalar(handle, this)? { + Some(Handle::Thread(thread)) => + this.detach_thread(thread, /*allow_terminated_joined*/ true)?, + _ => this.invalid_handle("CloseHandle")?, + } + + Ok(()) + } +} diff --git a/src/tools/miri/src/shims/windows/mod.rs b/src/tools/miri/src/shims/windows/mod.rs new file mode 100644 index 0000000000000..40fe71b2dbd02 --- /dev/null +++ b/src/tools/miri/src/shims/windows/mod.rs @@ -0,0 +1,6 @@ +pub mod dlsym; +pub mod foreign_items; + +mod handle; +mod sync; +mod thread; diff --git a/src/tools/miri/src/shims/windows/sync.rs b/src/tools/miri/src/shims/windows/sync.rs new file mode 100644 index 0000000000000..dc1052a824def --- /dev/null +++ b/src/tools/miri/src/shims/windows/sync.rs @@ -0,0 +1,138 @@ +use crate::*; + +// Locks are pointer-sized pieces of data, initialized to 0. +// We use the first 4 bytes to store the RwLockId. + +fn srwlock_get_or_create_id<'mir, 'tcx: 'mir>( + ecx: &mut MiriInterpCx<'mir, 'tcx>, + lock_op: &OpTy<'tcx, Provenance>, +) -> InterpResult<'tcx, RwLockId> { + let value_place = ecx.deref_operand_and_offset(lock_op, 0, ecx.machine.layouts.u32)?; + + ecx.rwlock_get_or_create(|ecx, next_id| { + let (old, success) = ecx + .atomic_compare_exchange_scalar( + &value_place, + &ImmTy::from_uint(0u32, ecx.machine.layouts.u32), + next_id.to_u32_scalar(), + AtomicRwOrd::Relaxed, + AtomicReadOrd::Relaxed, + false, + )? + .to_scalar_pair(); + + Ok(if success.to_bool().expect("compare_exchange's second return value is a bool") { + // Caller of the closure needs to allocate next_id + None + } else { + Some(RwLockId::from_u32(old.to_u32().expect("layout is u32"))) + }) + }) +} + +impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + #[allow(non_snake_case)] + fn AcquireSRWLockExclusive(&mut self, lock_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + let id = srwlock_get_or_create_id(this, lock_op)?; + let active_thread = this.get_active_thread(); + + if this.rwlock_is_locked(id) { + // Note: this will deadlock if the lock is already locked by this + // thread in any way. + // + // FIXME: Detect and report the deadlock proactively. (We currently + // report the deadlock only when no thread can continue execution, + // but we could detect that this lock is already locked and report + // an error.) + this.rwlock_enqueue_and_block_writer(id, active_thread); + } else { + this.rwlock_writer_lock(id, active_thread); + } + + Ok(()) + } + + #[allow(non_snake_case)] + fn TryAcquireSRWLockExclusive( + &mut self, + lock_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, u8> { + let this = self.eval_context_mut(); + let id = srwlock_get_or_create_id(this, lock_op)?; + let active_thread = this.get_active_thread(); + + if this.rwlock_is_locked(id) { + // Lock is already held. + Ok(0) + } else { + this.rwlock_writer_lock(id, active_thread); + Ok(1) + } + } + + #[allow(non_snake_case)] + fn ReleaseSRWLockExclusive(&mut self, lock_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + let id = srwlock_get_or_create_id(this, lock_op)?; + let active_thread = this.get_active_thread(); + + if !this.rwlock_writer_unlock(id, active_thread) { + // The docs do not say anything about this case, but it seems better to not allow it. + throw_ub_format!( + "calling ReleaseSRWLockExclusive on an SRWLock that is not exclusively locked by the current thread" + ); + } + + Ok(()) + } + + #[allow(non_snake_case)] + fn AcquireSRWLockShared(&mut self, lock_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + let id = srwlock_get_or_create_id(this, lock_op)?; + let active_thread = this.get_active_thread(); + + if this.rwlock_is_write_locked(id) { + this.rwlock_enqueue_and_block_reader(id, active_thread); + } else { + this.rwlock_reader_lock(id, active_thread); + } + + Ok(()) + } + + #[allow(non_snake_case)] + fn TryAcquireSRWLockShared( + &mut self, + lock_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, u8> { + let this = self.eval_context_mut(); + let id = srwlock_get_or_create_id(this, lock_op)?; + let active_thread = this.get_active_thread(); + + if this.rwlock_is_write_locked(id) { + Ok(0) + } else { + this.rwlock_reader_lock(id, active_thread); + Ok(1) + } + } + + #[allow(non_snake_case)] + fn ReleaseSRWLockShared(&mut self, lock_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + let id = srwlock_get_or_create_id(this, lock_op)?; + let active_thread = this.get_active_thread(); + + if !this.rwlock_reader_unlock(id, active_thread) { + // The docs do not say anything about this case, but it seems better to not allow it. + throw_ub_format!( + "calling ReleaseSRWLockShared on an SRWLock that is not locked by the current thread" + ); + } + + Ok(()) + } +} diff --git a/src/tools/miri/src/shims/windows/thread.rs b/src/tools/miri/src/shims/windows/thread.rs new file mode 100644 index 0000000000000..5ed0cb92f9e34 --- /dev/null +++ b/src/tools/miri/src/shims/windows/thread.rs @@ -0,0 +1,84 @@ +use rustc_middle::ty::layout::LayoutOf; +use rustc_target::spec::abi::Abi; + +use crate::*; +use shims::windows::handle::{EvalContextExt as _, Handle, PseudoHandle}; + +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} + +#[allow(non_snake_case)] +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + fn CreateThread( + &mut self, + security_op: &OpTy<'tcx, Provenance>, + stacksize_op: &OpTy<'tcx, Provenance>, + start_op: &OpTy<'tcx, Provenance>, + arg_op: &OpTy<'tcx, Provenance>, + flags_op: &OpTy<'tcx, Provenance>, + thread_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, ThreadId> { + let this = self.eval_context_mut(); + + let security = this.read_pointer(security_op)?; + // stacksize is ignored, but still needs to be a valid usize + this.read_scalar(stacksize_op)?.to_machine_usize(this)?; + let start_routine = this.read_pointer(start_op)?; + let func_arg = this.read_immediate(arg_op)?; + let flags = this.read_scalar(flags_op)?.to_u32()?; + + let thread = if this.ptr_is_null(this.read_pointer(thread_op)?)? { + None + } else { + let thread_info_place = this.deref_operand(thread_op)?; + Some(thread_info_place) + }; + + let stack_size_param_is_a_reservation = + this.eval_windows("c", "STACK_SIZE_PARAM_IS_A_RESERVATION")?.to_u32()?; + + // We ignore the stack size, so we also ignore the + // `STACK_SIZE_PARAM_IS_A_RESERVATION` flag. + if flags != 0 && flags != stack_size_param_is_a_reservation { + throw_unsup_format!("unsupported `dwCreationFlags` {} in `CreateThread`", flags) + } + + if !this.ptr_is_null(security)? { + throw_unsup_format!("non-null `lpThreadAttributes` in `CreateThread`") + } + + this.start_thread( + thread, + start_routine, + Abi::System { unwind: false }, + func_arg, + this.layout_of(this.tcx.types.u32)?, + ) + } + + fn WaitForSingleObject( + &mut self, + handle_op: &OpTy<'tcx, Provenance>, + timeout_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, u32> { + let this = self.eval_context_mut(); + + let handle = this.read_scalar(handle_op)?; + let timeout = this.read_scalar(timeout_op)?.to_u32()?; + + let thread = match Handle::from_scalar(handle, this)? { + Some(Handle::Thread(thread)) => thread, + // Unlike on posix, the outcome of joining the current thread is not documented. + // On current Windows, it just deadlocks. + Some(Handle::Pseudo(PseudoHandle::CurrentThread)) => this.get_active_thread(), + _ => this.invalid_handle("WaitForSingleObject")?, + }; + + if timeout != this.eval_windows("c", "INFINITE")?.to_u32()? { + throw_unsup_format!("`WaitForSingleObject` with non-infinite timeout"); + } + + this.join_thread(thread)?; + + Ok(0) + } +} diff --git a/src/tools/miri/src/stacked_borrows/diagnostics.rs b/src/tools/miri/src/stacked_borrows/diagnostics.rs new file mode 100644 index 0000000000000..0d76ed4e30878 --- /dev/null +++ b/src/tools/miri/src/stacked_borrows/diagnostics.rs @@ -0,0 +1,509 @@ +use smallvec::SmallVec; +use std::fmt; + +use rustc_middle::mir::interpret::{alloc_range, AllocId, AllocRange}; +use rustc_span::{Span, SpanData}; +use rustc_target::abi::Size; + +use crate::helpers::CurrentSpan; +use crate::stacked_borrows::{err_sb_ub, AccessKind, GlobalStateInner, Permission}; +use crate::*; + +use rustc_middle::mir::interpret::InterpError; + +#[derive(Clone, Debug)] +pub struct AllocHistory { + id: AllocId, + base: (Item, Span), + creations: smallvec::SmallVec<[Creation; 1]>, + invalidations: smallvec::SmallVec<[Invalidation; 1]>, + protectors: smallvec::SmallVec<[Protection; 1]>, +} + +#[derive(Clone, Debug)] +struct Creation { + retag: RetagOp, + span: Span, +} + +impl Creation { + fn generate_diagnostic(&self) -> (String, SpanData) { + let tag = self.retag.new_tag; + if let Some(perm) = self.retag.permission { + ( + format!( + "{tag:?} was created by a {:?} retag at offsets {:?}", + perm, self.retag.range, + ), + self.span.data(), + ) + } else { + assert!(self.retag.range.size == Size::ZERO); + ( + format!( + "{tag:?} would have been created here, but this is a zero-size retag ({:?}) so the tag in question does not exist anywhere", + self.retag.range, + ), + self.span.data(), + ) + } + } +} + +#[derive(Clone, Debug)] +struct Invalidation { + tag: SbTag, + range: AllocRange, + span: Span, + cause: InvalidationCause, +} + +#[derive(Clone, Debug)] +enum InvalidationCause { + Access(AccessKind), + Retag(Permission, RetagCause), +} + +impl Invalidation { + fn generate_diagnostic(&self) -> (String, SpanData) { + ( + format!( + "{:?} was later invalidated at offsets {:?} by a {}", + self.tag, self.range, self.cause + ), + self.span.data(), + ) + } +} + +impl fmt::Display for InvalidationCause { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + InvalidationCause::Access(kind) => write!(f, "{}", kind), + InvalidationCause::Retag(perm, kind) => + if *kind == RetagCause::FnEntry { + write!(f, "{:?} FnEntry retag", perm) + } else { + write!(f, "{:?} retag", perm) + }, + } + } +} + +#[derive(Clone, Debug)] +struct Protection { + tag: SbTag, + span: Span, +} + +#[derive(Clone)] +pub struct TagHistory { + pub created: (String, SpanData), + pub invalidated: Option<(String, SpanData)>, + pub protected: Option<(String, SpanData)>, +} + +pub struct DiagnosticCxBuilder<'span, 'ecx, 'mir, 'tcx> { + operation: Operation, + // 'span cannot be merged with any other lifetime since they appear invariantly, under the + // mutable ref. + current_span: &'span mut CurrentSpan<'ecx, 'mir, 'tcx>, + threads: &'ecx ThreadManager<'mir, 'tcx>, +} + +pub struct DiagnosticCx<'span, 'history, 'ecx, 'mir, 'tcx> { + operation: Operation, + // 'span and 'history cannot be merged, since when we call `unbuild` we need + // to return the exact 'span that was used when calling `build`. + current_span: &'span mut CurrentSpan<'ecx, 'mir, 'tcx>, + threads: &'ecx ThreadManager<'mir, 'tcx>, + history: &'history mut AllocHistory, + offset: Size, +} + +impl<'span, 'ecx, 'mir, 'tcx> DiagnosticCxBuilder<'span, 'ecx, 'mir, 'tcx> { + pub fn build<'history>( + self, + history: &'history mut AllocHistory, + offset: Size, + ) -> DiagnosticCx<'span, 'history, 'ecx, 'mir, 'tcx> { + DiagnosticCx { + operation: self.operation, + current_span: self.current_span, + threads: self.threads, + history, + offset, + } + } + + pub fn retag( + current_span: &'span mut CurrentSpan<'ecx, 'mir, 'tcx>, + threads: &'ecx ThreadManager<'mir, 'tcx>, + cause: RetagCause, + new_tag: SbTag, + orig_tag: ProvenanceExtra, + range: AllocRange, + ) -> Self { + let operation = + Operation::Retag(RetagOp { cause, new_tag, orig_tag, range, permission: None }); + + DiagnosticCxBuilder { current_span, threads, operation } + } + + pub fn read( + current_span: &'span mut CurrentSpan<'ecx, 'mir, 'tcx>, + threads: &'ecx ThreadManager<'mir, 'tcx>, + tag: ProvenanceExtra, + range: AllocRange, + ) -> Self { + let operation = Operation::Access(AccessOp { kind: AccessKind::Read, tag, range }); + DiagnosticCxBuilder { current_span, threads, operation } + } + + pub fn write( + current_span: &'span mut CurrentSpan<'ecx, 'mir, 'tcx>, + threads: &'ecx ThreadManager<'mir, 'tcx>, + tag: ProvenanceExtra, + range: AllocRange, + ) -> Self { + let operation = Operation::Access(AccessOp { kind: AccessKind::Write, tag, range }); + DiagnosticCxBuilder { current_span, threads, operation } + } + + pub fn dealloc( + current_span: &'span mut CurrentSpan<'ecx, 'mir, 'tcx>, + threads: &'ecx ThreadManager<'mir, 'tcx>, + tag: ProvenanceExtra, + ) -> Self { + let operation = Operation::Dealloc(DeallocOp { tag }); + DiagnosticCxBuilder { current_span, threads, operation } + } +} + +impl<'span, 'history, 'ecx, 'mir, 'tcx> DiagnosticCx<'span, 'history, 'ecx, 'mir, 'tcx> { + pub fn unbuild(self) -> DiagnosticCxBuilder<'span, 'ecx, 'mir, 'tcx> { + DiagnosticCxBuilder { + operation: self.operation, + current_span: self.current_span, + threads: self.threads, + } + } +} + +#[derive(Debug, Clone)] +enum Operation { + Retag(RetagOp), + Access(AccessOp), + Dealloc(DeallocOp), +} + +#[derive(Debug, Clone)] +struct RetagOp { + cause: RetagCause, + new_tag: SbTag, + orig_tag: ProvenanceExtra, + range: AllocRange, + permission: Option, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum RetagCause { + Normal, + FnReturn, + FnEntry, + TwoPhase, +} + +#[derive(Debug, Clone)] +struct AccessOp { + kind: AccessKind, + tag: ProvenanceExtra, + range: AllocRange, +} + +#[derive(Debug, Clone)] +struct DeallocOp { + tag: ProvenanceExtra, +} + +impl AllocHistory { + pub fn new(id: AllocId, item: Item, current_span: &mut CurrentSpan<'_, '_, '_>) -> Self { + Self { + id, + base: (item, current_span.get()), + creations: SmallVec::new(), + invalidations: SmallVec::new(), + protectors: SmallVec::new(), + } + } +} + +impl<'span, 'history, 'ecx, 'mir, 'tcx> DiagnosticCx<'span, 'history, 'ecx, 'mir, 'tcx> { + pub fn start_grant(&mut self, perm: Permission) { + let Operation::Retag(op) = &mut self.operation else { + unreachable!("start_grant must only be called during a retag, this is: {:?}", self.operation) + }; + op.permission = Some(perm); + + let last_creation = &mut self.history.creations.last_mut().unwrap(); + match last_creation.retag.permission { + None => { + last_creation.retag.permission = Some(perm); + } + Some(previous) => + if previous != perm { + // 'Split up' the creation event. + let previous_range = last_creation.retag.range; + last_creation.retag.range = alloc_range(previous_range.start, self.offset); + let mut new_event = last_creation.clone(); + new_event.retag.range = alloc_range(self.offset, previous_range.end()); + new_event.retag.permission = Some(perm); + self.history.creations.push(new_event); + }, + } + } + + pub fn log_creation(&mut self) { + let Operation::Retag(op) = &self.operation else { + unreachable!("log_creation must only be called during a retag") + }; + self.history.creations.push(Creation { retag: op.clone(), span: self.current_span.get() }); + } + + pub fn log_invalidation(&mut self, tag: SbTag) { + let mut span = self.current_span.get(); + let (range, cause) = match &self.operation { + Operation::Retag(RetagOp { cause, range, permission, .. }) => { + if *cause == RetagCause::FnEntry { + span = self.current_span.get_parent(); + } + (*range, InvalidationCause::Retag(permission.unwrap(), *cause)) + } + Operation::Access(AccessOp { kind, range, .. }) => + (*range, InvalidationCause::Access(*kind)), + _ => unreachable!("Tags can only be invalidated during a retag or access"), + }; + self.history.invalidations.push(Invalidation { tag, range, span, cause }); + } + + pub fn log_protector(&mut self) { + let Operation::Retag(op) = &self.operation else { + unreachable!("Protectors can only be created during a retag") + }; + self.history.protectors.push(Protection { tag: op.new_tag, span: self.current_span.get() }); + } + + pub fn get_logs_relevant_to( + &self, + tag: SbTag, + protector_tag: Option, + ) -> Option { + let Some(created) = self.history + .creations + .iter() + .rev() + .find_map(|event| { + // First, look for a Creation event where the tag and the offset matches. This + // ensrues that we pick the right Creation event when a retag isn't uniform due to + // Freeze. + let range = event.retag.range; + if event.retag.new_tag == tag + && self.offset >= range.start + && self.offset < (range.start + range.size) + { + Some(event.generate_diagnostic()) + } else { + None + } + }) + .or_else(|| { + // If we didn't find anything with a matching offset, just return the event where + // the tag was created. This branch is hit when we use a tag at an offset that + // doesn't have the tag. + self.history.creations.iter().rev().find_map(|event| { + if event.retag.new_tag == tag { + Some(event.generate_diagnostic()) + } else { + None + } + }) + }).or_else(|| { + // If we didn't find a retag that created this tag, it might be the base tag of + // this allocation. + if self.history.base.0.tag() == tag { + Some(( + format!("{:?} was created here, as the base tag for {:?}", tag, self.history.id), + self.history.base.1.data() + )) + } else { + None + } + }) else { + // But if we don't have a creation event, this is related to a wildcard, and there + // is really nothing we can do to help. + return None; + }; + + let invalidated = self.history.invalidations.iter().rev().find_map(|event| { + if event.tag == tag { Some(event.generate_diagnostic()) } else { None } + }); + + let protected = protector_tag + .and_then(|protector| { + self.history.protectors.iter().find(|protection| protection.tag == protector) + }) + .map(|protection| { + let protected_tag = protection.tag; + (format!("{protected_tag:?} is this argument"), protection.span.data()) + }); + + Some(TagHistory { created, invalidated, protected }) + } + + /// Report a descriptive error when `new` could not be granted from `derived_from`. + #[inline(never)] // This is only called on fatal code paths + pub fn grant_error(&self, perm: Permission, stack: &Stack) -> InterpError<'tcx> { + let Operation::Retag(op) = &self.operation else { + unreachable!("grant_error should only be called during a retag") + }; + let action = format!( + "trying to retag from {:?} for {:?} permission at {:?}[{:#x}]", + op.orig_tag, + perm, + self.history.id, + self.offset.bytes(), + ); + err_sb_ub( + format!("{}{}", action, error_cause(stack, op.orig_tag)), + Some(operation_summary(&op.cause.summary(), self.history.id, op.range)), + op.orig_tag.and_then(|orig_tag| self.get_logs_relevant_to(orig_tag, None)), + ) + } + + /// Report a descriptive error when `access` is not permitted based on `tag`. + #[inline(never)] // This is only called on fatal code paths + pub fn access_error(&self, stack: &Stack) -> InterpError<'tcx> { + let Operation::Access(op) = &self.operation else { + unreachable!("access_error should only be called during an access") + }; + let action = format!( + "attempting a {access} using {tag:?} at {alloc_id:?}[{offset:#x}]", + access = op.kind, + tag = op.tag, + alloc_id = self.history.id, + offset = self.offset.bytes(), + ); + err_sb_ub( + format!("{}{}", action, error_cause(stack, op.tag)), + Some(operation_summary("an access", self.history.id, op.range)), + op.tag.and_then(|tag| self.get_logs_relevant_to(tag, None)), + ) + } + + #[inline(never)] // This is only called on fatal code paths + pub fn protector_error(&self, item: &Item) -> InterpError<'tcx> { + let call_id = self + .threads + .all_stacks() + .flatten() + .map(|frame| { + frame.extra.stacked_borrows.as_ref().expect("we should have Stacked Borrows data") + }) + .find(|frame| frame.protected_tags.contains(&item.tag())) + .map(|frame| frame.call_id) + .unwrap(); // FIXME: Surely we should find something, but a panic seems wrong here? + match self.operation { + Operation::Dealloc(_) => + err_sb_ub( + format!( + "deallocating while item {:?} is protected by call {:?}", + item, call_id + ), + None, + None, + ), + Operation::Retag(RetagOp { orig_tag: tag, .. }) + | Operation::Access(AccessOp { tag, .. }) => + err_sb_ub( + format!( + "not granting access to tag {:?} because that would remove {:?} which is protected because it is an argument of call {:?}", + tag, item, call_id + ), + None, + tag.and_then(|tag| self.get_logs_relevant_to(tag, Some(item.tag()))), + ), + } + } + + #[inline(never)] // This is only called on fatal code paths + pub fn dealloc_error(&self) -> InterpError<'tcx> { + let Operation::Dealloc(op) = &self.operation else { + unreachable!("dealloc_error should only be called during a deallocation") + }; + err_sb_ub( + format!( + "no item granting write access for deallocation to tag {:?} at {:?} found in borrow stack", + op.tag, self.history.id, + ), + None, + op.tag.and_then(|tag| self.get_logs_relevant_to(tag, None)), + ) + } + + #[inline(never)] + pub fn check_tracked_tag_popped(&self, item: &Item, global: &GlobalStateInner) { + if !global.tracked_pointer_tags.contains(&item.tag()) { + return; + } + let summary = match self.operation { + Operation::Dealloc(_) => None, + Operation::Access(AccessOp { kind, tag, .. }) => Some((tag, kind)), + Operation::Retag(RetagOp { orig_tag, permission, .. }) => { + let kind = match permission + .expect("start_grant should set the current permission before popping a tag") + { + Permission::SharedReadOnly => AccessKind::Read, + Permission::Unique => AccessKind::Write, + Permission::SharedReadWrite | Permission::Disabled => { + panic!("Only SharedReadOnly and Unique retags can pop tags"); + } + }; + Some((orig_tag, kind)) + } + }; + self.current_span + .machine() + .emit_diagnostic(NonHaltingDiagnostic::PoppedPointerTag(*item, summary)); + } +} + +fn operation_summary(operation: &str, alloc_id: AllocId, alloc_range: AllocRange) -> String { + format!("this error occurs as part of {operation} at {alloc_id:?}{alloc_range:?}") +} + +fn error_cause(stack: &Stack, prov_extra: ProvenanceExtra) -> &'static str { + if let ProvenanceExtra::Concrete(tag) = prov_extra { + if (0..stack.len()) + .map(|i| stack.get(i).unwrap()) + .any(|item| item.tag() == tag && item.perm() != Permission::Disabled) + { + ", but that tag only grants SharedReadOnly permission for this location" + } else { + ", but that tag does not exist in the borrow stack for this location" + } + } else { + ", but no exposed tags have suitable permission in the borrow stack for this location" + } +} + +impl RetagCause { + fn summary(&self) -> String { + match self { + RetagCause::Normal => "retag", + RetagCause::FnEntry => "FnEntry retag", + RetagCause::FnReturn => "FnReturn retag", + RetagCause::TwoPhase => "two-phase retag", + } + .to_string() + } +} diff --git a/src/tools/miri/src/stacked_borrows/item.rs b/src/tools/miri/src/stacked_borrows/item.rs new file mode 100644 index 0000000000000..709b27d191b26 --- /dev/null +++ b/src/tools/miri/src/stacked_borrows/item.rs @@ -0,0 +1,104 @@ +use crate::stacked_borrows::SbTag; +use std::fmt; +use std::num::NonZeroU64; + +/// An item in the per-location borrow stack. +#[derive(Copy, Clone, Hash, PartialEq, Eq)] +pub struct Item(u64); + +// An Item contains 3 bitfields: +// * Bits 0-61 store an SbTag +// * Bits 61-63 store a Permission +// * Bit 64 stores a flag which indicates if we have a protector +const TAG_MASK: u64 = u64::MAX >> 3; +const PERM_MASK: u64 = 0x3 << 61; +const PROTECTED_MASK: u64 = 0x1 << 63; + +const PERM_SHIFT: u64 = 61; +const PROTECTED_SHIFT: u64 = 63; + +impl Item { + pub fn new(tag: SbTag, perm: Permission, protected: bool) -> Self { + assert!(tag.0.get() <= TAG_MASK); + let packed_tag = tag.0.get(); + let packed_perm = perm.to_bits() << PERM_SHIFT; + let packed_protected = u64::from(protected) << PROTECTED_SHIFT; + + let new = Self(packed_tag | packed_perm | packed_protected); + + debug_assert!(new.tag() == tag); + debug_assert!(new.perm() == perm); + debug_assert!(new.protected() == protected); + + new + } + + /// The pointers the permission is granted to. + pub fn tag(self) -> SbTag { + SbTag(NonZeroU64::new(self.0 & TAG_MASK).unwrap()) + } + + /// The permission this item grants. + pub fn perm(self) -> Permission { + Permission::from_bits((self.0 & PERM_MASK) >> PERM_SHIFT) + } + + /// Whether or not there is a protector for this tag + pub fn protected(self) -> bool { + self.0 & PROTECTED_MASK > 0 + } + + /// Set the Permission stored in this Item + pub fn set_permission(&mut self, perm: Permission) { + // Clear the current set permission + self.0 &= !PERM_MASK; + // Write Permission::Disabled to the Permission bits + self.0 |= perm.to_bits() << PERM_SHIFT; + } +} + +impl fmt::Debug for Item { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "[{:?} for {:?}]", self.perm(), self.tag()) + } +} + +/// Indicates which permission is granted (by this item to some pointers) +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +pub enum Permission { + /// Grants unique mutable access. + Unique, + /// Grants shared mutable access. + SharedReadWrite, + /// Grants shared read-only access. + SharedReadOnly, + /// Grants no access, but separates two groups of SharedReadWrite so they are not + /// all considered mutually compatible. + Disabled, +} + +impl Permission { + const UNIQUE: u64 = 0; + const SHARED_READ_WRITE: u64 = 1; + const SHARED_READ_ONLY: u64 = 2; + const DISABLED: u64 = 3; + + fn to_bits(self) -> u64 { + match self { + Permission::Unique => Self::UNIQUE, + Permission::SharedReadWrite => Self::SHARED_READ_WRITE, + Permission::SharedReadOnly => Self::SHARED_READ_ONLY, + Permission::Disabled => Self::DISABLED, + } + } + + fn from_bits(perm: u64) -> Self { + match perm { + Self::UNIQUE => Permission::Unique, + Self::SHARED_READ_WRITE => Permission::SharedReadWrite, + Self::SHARED_READ_ONLY => Permission::SharedReadOnly, + Self::DISABLED => Permission::Disabled, + _ => unreachable!(), + } + } +} diff --git a/src/tools/miri/src/stacked_borrows/mod.rs b/src/tools/miri/src/stacked_borrows/mod.rs new file mode 100644 index 0000000000000..f7f4b1357f106 --- /dev/null +++ b/src/tools/miri/src/stacked_borrows/mod.rs @@ -0,0 +1,1087 @@ +//! Implements "Stacked Borrows". See +//! for further information. + +use log::trace; +use std::cell::RefCell; +use std::cmp; +use std::fmt; +use std::num::NonZeroU64; + +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_hir::Mutability; +use rustc_middle::mir::RetagKind; +use rustc_middle::ty::{ + self, + layout::{HasParamEnv, LayoutOf}, + Ty, +}; +use rustc_span::DUMMY_SP; +use rustc_target::abi::Size; +use smallvec::SmallVec; + +use crate::*; + +pub mod diagnostics; +use diagnostics::{AllocHistory, DiagnosticCx, DiagnosticCxBuilder, RetagCause, TagHistory}; + +mod item; +pub use item::{Item, Permission}; +mod stack; +pub use stack::Stack; + +pub type CallId = NonZeroU64; + +// Even reading memory can have effects on the stack, so we need a `RefCell` here. +pub type AllocExtra = RefCell; + +/// Tracking pointer provenance +#[derive(Copy, Clone, Hash, PartialEq, Eq)] +pub struct SbTag(NonZeroU64); + +impl SbTag { + pub fn new(i: u64) -> Option { + NonZeroU64::new(i).map(SbTag) + } + + // The default to be used when SB is disabled + pub fn default() -> Self { + Self::new(1).unwrap() + } +} + +impl fmt::Debug for SbTag { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "<{}>", self.0) + } +} + +#[derive(Debug)] +pub struct FrameExtra { + /// The ID of the call this frame corresponds to. + call_id: CallId, + + /// If this frame is protecting any tags, they are listed here. We use this list to do + /// incremental updates of the global list of protected tags stored in the + /// `stacked_borrows::GlobalState` upon function return, and if we attempt to pop a protected + /// tag, to identify which call is responsible for protecting the tag. + /// See `Stack::item_popped` for more explanation. + /// + /// This will contain one tag per reference passed to the function, so + /// a size of 2 is enough for the vast majority of functions. + protected_tags: SmallVec<[SbTag; 2]>, +} + +/// Extra per-allocation state. +#[derive(Clone, Debug)] +pub struct Stacks { + // Even reading memory can have effects on the stack, so we need a `RefCell` here. + stacks: RangeMap, + /// Stores past operations on this allocation + history: AllocHistory, + /// The set of tags that have been exposed inside this allocation. + exposed_tags: FxHashSet, + /// Whether this memory has been modified since the last time the tag GC ran + modified_since_last_gc: bool, +} + +/// Extra global state, available to the memory access hooks. +#[derive(Debug)] +pub struct GlobalStateInner { + /// Next unused pointer ID (tag). + next_ptr_tag: SbTag, + /// Table storing the "base" tag for each allocation. + /// The base tag is the one used for the initial pointer. + /// We need this in a separate table to handle cyclic statics. + base_ptr_tags: FxHashMap, + /// Next unused call ID (for protectors). + next_call_id: CallId, + /// All currently protected tags. + /// An item is protected if its tag is in this set, *and* it has the "protected" bit set. + /// We add tags to this when they are created with a protector in `reborrow`, and + /// we remove tags from this when the call which is protecting them returns, in + /// `GlobalStateInner::end_call`. See `Stack::item_popped` for more details. + protected_tags: FxHashSet, + /// The pointer ids to trace + tracked_pointer_tags: FxHashSet, + /// The call ids to trace + tracked_call_ids: FxHashSet, + /// Whether to recurse into datatypes when searching for pointers to retag. + retag_fields: bool, +} + +/// We need interior mutable access to the global state. +pub type GlobalState = RefCell; + +/// Indicates which kind of access is being performed. +#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)] +pub enum AccessKind { + Read, + Write, +} + +impl fmt::Display for AccessKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + AccessKind::Read => write!(f, "read access"), + AccessKind::Write => write!(f, "write access"), + } + } +} + +/// Indicates which kind of reference is being created. +/// Used by high-level `reborrow` to compute which permissions to grant to the +/// new pointer. +#[derive(Copy, Clone, Hash, PartialEq, Eq)] +pub enum RefKind { + /// `&mut` and `Box`. + Unique { two_phase: bool }, + /// `&` with or without interior mutability. + Shared, + /// `*mut`/`*const` (raw pointers). + Raw { mutable: bool }, +} + +impl fmt::Display for RefKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + RefKind::Unique { two_phase: false } => write!(f, "unique reference"), + RefKind::Unique { two_phase: true } => write!(f, "unique reference (two-phase)"), + RefKind::Shared => write!(f, "shared reference"), + RefKind::Raw { mutable: true } => write!(f, "raw (mutable) pointer"), + RefKind::Raw { mutable: false } => write!(f, "raw (constant) pointer"), + } + } +} + +/// Utilities for initialization and ID generation +impl GlobalStateInner { + pub fn new( + tracked_pointer_tags: FxHashSet, + tracked_call_ids: FxHashSet, + retag_fields: bool, + ) -> Self { + GlobalStateInner { + next_ptr_tag: SbTag(NonZeroU64::new(1).unwrap()), + base_ptr_tags: FxHashMap::default(), + next_call_id: NonZeroU64::new(1).unwrap(), + protected_tags: FxHashSet::default(), + tracked_pointer_tags, + tracked_call_ids, + retag_fields, + } + } + + /// Generates a new pointer tag. Remember to also check track_pointer_tags and log its creation! + fn new_ptr(&mut self) -> SbTag { + let id = self.next_ptr_tag; + self.next_ptr_tag = SbTag(NonZeroU64::new(id.0.get() + 1).unwrap()); + id + } + + pub fn new_frame(&mut self, machine: &MiriMachine<'_, '_>) -> FrameExtra { + let call_id = self.next_call_id; + trace!("new_frame: Assigning call ID {}", call_id); + if self.tracked_call_ids.contains(&call_id) { + machine.emit_diagnostic(NonHaltingDiagnostic::CreatedCallId(call_id)); + } + self.next_call_id = NonZeroU64::new(call_id.get() + 1).unwrap(); + FrameExtra { call_id, protected_tags: SmallVec::new() } + } + + pub fn end_call(&mut self, frame: &machine::FrameData<'_>) { + for tag in &frame + .stacked_borrows + .as_ref() + .expect("we should have Stacked Borrows data") + .protected_tags + { + self.protected_tags.remove(tag); + } + } + + pub fn base_ptr_tag(&mut self, id: AllocId, machine: &MiriMachine<'_, '_>) -> SbTag { + self.base_ptr_tags.get(&id).copied().unwrap_or_else(|| { + let tag = self.new_ptr(); + if self.tracked_pointer_tags.contains(&tag) { + machine.emit_diagnostic(NonHaltingDiagnostic::CreatedPointerTag(tag.0, None)); + } + trace!("New allocation {:?} has base tag {:?}", id, tag); + self.base_ptr_tags.try_insert(id, tag).unwrap(); + tag + }) + } +} + +/// Error reporting +pub fn err_sb_ub<'tcx>( + msg: String, + help: Option, + history: Option, +) -> InterpError<'tcx> { + err_machine_stop!(TerminationInfo::StackedBorrowsUb { msg, help, history }) +} + +// # Stacked Borrows Core Begin + +/// We need to make at least the following things true: +/// +/// U1: After creating a `Uniq`, it is at the top. +/// U2: If the top is `Uniq`, accesses must be through that `Uniq` or remove it it. +/// U3: If an access happens with a `Uniq`, it requires the `Uniq` to be in the stack. +/// +/// F1: After creating a `&`, the parts outside `UnsafeCell` have our `SharedReadOnly` on top. +/// F2: If a write access happens, it pops the `SharedReadOnly`. This has three pieces: +/// F2a: If a write happens granted by an item below our `SharedReadOnly`, the `SharedReadOnly` +/// gets popped. +/// F2b: No `SharedReadWrite` or `Unique` will ever be added on top of our `SharedReadOnly`. +/// F3: If an access happens with an `&` outside `UnsafeCell`, +/// it requires the `SharedReadOnly` to still be in the stack. + +/// Core relation on `Permission` to define which accesses are allowed +impl Permission { + /// This defines for a given permission, whether it permits the given kind of access. + fn grants(self, access: AccessKind) -> bool { + // Disabled grants nothing. Otherwise, all items grant read access, and except for SharedReadOnly they grant write access. + self != Permission::Disabled + && (access == AccessKind::Read || self != Permission::SharedReadOnly) + } +} + +/// Core per-location operations: access, dealloc, reborrow. +impl<'tcx> Stack { + /// Find the first write-incompatible item above the given one -- + /// i.e, find the height to which the stack will be truncated when writing to `granting`. + fn find_first_write_incompatible(&self, granting: usize) -> usize { + let perm = self.get(granting).unwrap().perm(); + match perm { + Permission::SharedReadOnly => bug!("Cannot use SharedReadOnly for writing"), + Permission::Disabled => bug!("Cannot use Disabled for anything"), + Permission::Unique => { + // On a write, everything above us is incompatible. + granting + 1 + } + Permission::SharedReadWrite => { + // The SharedReadWrite *just* above us are compatible, to skip those. + let mut idx = granting + 1; + while let Some(item) = self.get(idx) { + if item.perm() == Permission::SharedReadWrite { + // Go on. + idx += 1; + } else { + // Found first incompatible! + break; + } + } + idx + } + } + } + + /// Check if the given item is protected. + /// + /// The `provoking_access` argument is only used to produce diagnostics. + /// It is `Some` when we are granting the contained access for said tag, and it is + /// `None` during a deallocation. + /// Within `provoking_access, the `AllocRange` refers the entire operation, and + /// the `Size` refers to the specific location in the `AllocRange` that we are + /// currently checking. + fn item_popped( + item: &Item, + global: &GlobalStateInner, + dcx: &mut DiagnosticCx<'_, '_, '_, '_, 'tcx>, + ) -> InterpResult<'tcx> { + if !global.tracked_pointer_tags.is_empty() { + dcx.check_tracked_tag_popped(item, global); + } + + if !item.protected() { + return Ok(()); + } + + // We store tags twice, once in global.protected_tags and once in each call frame. + // We do this because consulting a single global set in this function is faster + // than attempting to search all call frames in the program for the `FrameExtra` + // (if any) which is protecting the popped tag. + // + // This duplication trades off making `end_call` slower to make this function faster. This + // trade-off is profitable in practice for a combination of two reasons. + // 1. A single protected tag can (and does in some programs) protect thousands of `Item`s. + // Therefore, adding overhead in function call/return is profitable even if it only + // saves a little work in this function. + // 2. Most frames protect only one or two tags. So this duplicative global turns a search + // which ends up about linear in the number of protected tags in the program into a + // constant time check (and a slow linear, because the tags in the frames aren't contiguous). + if global.protected_tags.contains(&item.tag()) { + return Err(dcx.protector_error(item).into()); + } + Ok(()) + } + + /// Test if a memory `access` using pointer tagged `tag` is granted. + /// If yes, return the index of the item that granted it. + /// `range` refers the entire operation, and `offset` refers to the specific offset into the + /// allocation that we are currently checking. + fn access( + &mut self, + access: AccessKind, + tag: ProvenanceExtra, + global: &mut GlobalStateInner, + dcx: &mut DiagnosticCx<'_, '_, '_, '_, 'tcx>, + exposed_tags: &FxHashSet, + ) -> InterpResult<'tcx> { + // Two main steps: Find granting item, remove incompatible items above. + + // Step 1: Find granting item. + let granting_idx = + self.find_granting(access, tag, exposed_tags).map_err(|_| dcx.access_error(self))?; + + // Step 2: Remove incompatible items above them. Make sure we do not remove protected + // items. Behavior differs for reads and writes. + // In case of wildcards/unknown matches, we remove everything that is *definitely* gone. + if access == AccessKind::Write { + // Remove everything above the write-compatible items, like a proper stack. This makes sure read-only and unique + // pointers become invalid on write accesses (ensures F2a, and ensures U2 for write accesses). + let first_incompatible_idx = if let Some(granting_idx) = granting_idx { + // The granting_idx *might* be approximate, but any lower idx would remove more + // things. Even if this is a Unique and the lower idx is an SRW (which removes + // less), there is an SRW group boundary here so strictly more would get removed. + self.find_first_write_incompatible(granting_idx) + } else { + // We are writing to something in the unknown part. + // There is a SRW group boundary between the unknown and the known, so everything is incompatible. + 0 + }; + self.pop_items_after(first_incompatible_idx, |item| { + Stack::item_popped(&item, global, dcx)?; + dcx.log_invalidation(item.tag()); + Ok(()) + })?; + } else { + // On a read, *disable* all `Unique` above the granting item. This ensures U2 for read accesses. + // The reason this is not following the stack discipline (by removing the first Unique and + // everything on top of it) is that in `let raw = &mut *x as *mut _; let _val = *x;`, the second statement + // would pop the `Unique` from the reborrow of the first statement, and subsequently also pop the + // `SharedReadWrite` for `raw`. + // This pattern occurs a lot in the standard library: create a raw pointer, then also create a shared + // reference and use that. + // We *disable* instead of removing `Unique` to avoid "connecting" two neighbouring blocks of SRWs. + let first_incompatible_idx = if let Some(granting_idx) = granting_idx { + // The granting_idx *might* be approximate, but any lower idx would disable more things. + granting_idx + 1 + } else { + // We are reading from something in the unknown part. That means *all* `Unique` we know about are dead now. + 0 + }; + self.disable_uniques_starting_at(first_incompatible_idx, |item| { + Stack::item_popped(&item, global, dcx)?; + dcx.log_invalidation(item.tag()); + Ok(()) + })?; + } + + // If this was an approximate action, we now collapse everything into an unknown. + if granting_idx.is_none() || matches!(tag, ProvenanceExtra::Wildcard) { + // Compute the upper bound of the items that remain. + // (This is why we did all the work above: to reduce the items we have to consider here.) + let mut max = NonZeroU64::new(1).unwrap(); + for i in 0..self.len() { + let item = self.get(i).unwrap(); + // Skip disabled items, they cannot be matched anyway. + if !matches!(item.perm(), Permission::Disabled) { + // We are looking for a strict upper bound, so add 1 to this tag. + max = cmp::max(item.tag().0.checked_add(1).unwrap(), max); + } + } + if let Some(unk) = self.unknown_bottom() { + max = cmp::max(unk.0, max); + } + // Use `max` as new strict upper bound for everything. + trace!( + "access: forgetting stack to upper bound {max} due to wildcard or unknown access" + ); + self.set_unknown_bottom(SbTag(max)); + } + + // Done. + Ok(()) + } + + /// Deallocate a location: Like a write access, but also there must be no + /// active protectors at all because we will remove all items. + fn dealloc( + &mut self, + tag: ProvenanceExtra, + global: &GlobalStateInner, + dcx: &mut DiagnosticCx<'_, '_, '_, '_, 'tcx>, + exposed_tags: &FxHashSet, + ) -> InterpResult<'tcx> { + // Step 1: Make sure there is a granting item. + self.find_granting(AccessKind::Write, tag, exposed_tags) + .map_err(|_| dcx.dealloc_error())?; + + // Step 2: Consider all items removed. This checks for protectors. + for idx in (0..self.len()).rev() { + let item = self.get(idx).unwrap(); + Stack::item_popped(&item, global, dcx)?; + } + + Ok(()) + } + + /// Derive a new pointer from one with the given tag. + /// `weak` controls whether this operation is weak or strong: weak granting does not act as + /// an access, and they add the new item directly on top of the one it is derived + /// from instead of all the way at the top of the stack. + /// `range` refers the entire operation, and `offset` refers to the specific location in + /// `range` that we are currently checking. + fn grant( + &mut self, + derived_from: ProvenanceExtra, + new: Item, + global: &mut GlobalStateInner, + dcx: &mut DiagnosticCx<'_, '_, '_, '_, 'tcx>, + exposed_tags: &FxHashSet, + ) -> InterpResult<'tcx> { + dcx.start_grant(new.perm()); + + // Figure out which access `perm` corresponds to. + let access = + if new.perm().grants(AccessKind::Write) { AccessKind::Write } else { AccessKind::Read }; + + // Now we figure out which item grants our parent (`derived_from`) this kind of access. + // We use that to determine where to put the new item. + let granting_idx = self + .find_granting(access, derived_from, exposed_tags) + .map_err(|_| dcx.grant_error(new.perm(), self))?; + + // Compute where to put the new item. + // Either way, we ensure that we insert the new item in a way such that between + // `derived_from` and the new one, there are only items *compatible with* `derived_from`. + let new_idx = if new.perm() == Permission::SharedReadWrite { + assert!( + access == AccessKind::Write, + "this case only makes sense for stack-like accesses" + ); + + let (Some(granting_idx), ProvenanceExtra::Concrete(_)) = (granting_idx, derived_from) else { + // The parent is a wildcard pointer or matched the unknown bottom. + // This is approximate. Nobody knows what happened, so forget everything. + // The new thing is SRW anyway, so we cannot push it "on top of the unkown part" + // (for all we know, it might join an SRW group inside the unknown). + trace!("reborrow: forgetting stack entirely due to SharedReadWrite reborrow from wildcard or unknown"); + self.set_unknown_bottom(global.next_ptr_tag); + return Ok(()); + }; + + // SharedReadWrite can coexist with "existing loans", meaning they don't act like a write + // access. Instead of popping the stack, we insert the item at the place the stack would + // be popped to (i.e., we insert it above all the write-compatible items). + // This ensures F2b by adding the new item below any potentially existing `SharedReadOnly`. + self.find_first_write_incompatible(granting_idx) + } else { + // A "safe" reborrow for a pointer that actually expects some aliasing guarantees. + // Here, creating a reference actually counts as an access. + // This ensures F2b for `Unique`, by removing offending `SharedReadOnly`. + self.access(access, derived_from, global, dcx, exposed_tags)?; + + // We insert "as far up as possible": We know only compatible items are remaining + // on top of `derived_from`, and we want the new item at the top so that we + // get the strongest possible guarantees. + // This ensures U1 and F1. + self.len() + }; + + // Put the new item there. + trace!("reborrow: adding item {:?}", new); + self.insert(new_idx, new); + Ok(()) + } +} +// # Stacked Borrows Core End + +/// Integration with the SbTag garbage collector +impl Stacks { + pub fn remove_unreachable_tags(&mut self, live_tags: &FxHashSet) { + if self.modified_since_last_gc { + for stack in self.stacks.iter_mut_all() { + if stack.len() > 64 { + stack.retain(live_tags); + } + } + self.modified_since_last_gc = false; + } + } +} + +/// Map per-stack operations to higher-level per-location-range operations. +impl<'tcx> Stacks { + /// Creates a new stack with an initial tag. For diagnostic purposes, we also need to know + /// the [`AllocId`] of the allocation this is associated with. + fn new( + size: Size, + perm: Permission, + tag: SbTag, + id: AllocId, + current_span: &mut CurrentSpan<'_, '_, '_>, + ) -> Self { + let item = Item::new(tag, perm, false); + let stack = Stack::new(item); + + Stacks { + stacks: RangeMap::new(size, stack), + history: AllocHistory::new(id, item, current_span), + exposed_tags: FxHashSet::default(), + modified_since_last_gc: false, + } + } + + /// Call `f` on every stack in the range. + fn for_each( + &mut self, + range: AllocRange, + mut dcx_builder: DiagnosticCxBuilder<'_, '_, '_, 'tcx>, + mut f: impl FnMut( + &mut Stack, + &mut DiagnosticCx<'_, '_, '_, '_, 'tcx>, + &mut FxHashSet, + ) -> InterpResult<'tcx>, + ) -> InterpResult<'tcx> { + self.modified_since_last_gc = true; + for (offset, stack) in self.stacks.iter_mut(range.start, range.size) { + let mut dcx = dcx_builder.build(&mut self.history, offset); + f(stack, &mut dcx, &mut self.exposed_tags)?; + dcx_builder = dcx.unbuild(); + } + Ok(()) + } +} + +/// Glue code to connect with Miri Machine Hooks +impl Stacks { + pub fn new_allocation( + id: AllocId, + size: Size, + state: &GlobalState, + kind: MemoryKind, + mut current_span: CurrentSpan<'_, '_, '_>, + ) -> Self { + let mut extra = state.borrow_mut(); + let (base_tag, perm) = match kind { + // New unique borrow. This tag is not accessible by the program, + // so it will only ever be used when using the local directly (i.e., + // not through a pointer). That is, whenever we directly write to a local, this will pop + // everything else off the stack, invalidating all previous pointers, + // and in particular, *all* raw pointers. + MemoryKind::Stack => + (extra.base_ptr_tag(id, current_span.machine()), Permission::Unique), + // Everything else is shared by default. + _ => (extra.base_ptr_tag(id, current_span.machine()), Permission::SharedReadWrite), + }; + Stacks::new(size, perm, base_tag, id, &mut current_span) + } + + #[inline(always)] + pub fn before_memory_read<'tcx, 'mir, 'ecx>( + &mut self, + alloc_id: AllocId, + tag: ProvenanceExtra, + range: AllocRange, + state: &GlobalState, + mut current_span: CurrentSpan<'ecx, 'mir, 'tcx>, + threads: &'ecx ThreadManager<'mir, 'tcx>, + ) -> InterpResult<'tcx> + where + 'tcx: 'ecx, + { + trace!( + "read access with tag {:?}: {:?}, size {}", + tag, + Pointer::new(alloc_id, range.start), + range.size.bytes() + ); + let dcx = DiagnosticCxBuilder::read(&mut current_span, threads, tag, range); + let mut state = state.borrow_mut(); + self.for_each(range, dcx, |stack, dcx, exposed_tags| { + stack.access(AccessKind::Read, tag, &mut state, dcx, exposed_tags) + }) + } + + #[inline(always)] + pub fn before_memory_write<'tcx, 'mir, 'ecx>( + &mut self, + alloc_id: AllocId, + tag: ProvenanceExtra, + range: AllocRange, + state: &GlobalState, + mut current_span: CurrentSpan<'ecx, 'mir, 'tcx>, + threads: &'ecx ThreadManager<'mir, 'tcx>, + ) -> InterpResult<'tcx> { + trace!( + "write access with tag {:?}: {:?}, size {}", + tag, + Pointer::new(alloc_id, range.start), + range.size.bytes() + ); + let dcx = DiagnosticCxBuilder::write(&mut current_span, threads, tag, range); + let mut state = state.borrow_mut(); + self.for_each(range, dcx, |stack, dcx, exposed_tags| { + stack.access(AccessKind::Write, tag, &mut state, dcx, exposed_tags) + }) + } + + #[inline(always)] + pub fn before_memory_deallocation<'tcx, 'mir, 'ecx>( + &mut self, + alloc_id: AllocId, + tag: ProvenanceExtra, + range: AllocRange, + state: &GlobalState, + mut current_span: CurrentSpan<'ecx, 'mir, 'tcx>, + threads: &'ecx ThreadManager<'mir, 'tcx>, + ) -> InterpResult<'tcx> { + trace!("deallocation with tag {:?}: {:?}, size {}", tag, alloc_id, range.size.bytes()); + let dcx = DiagnosticCxBuilder::dealloc(&mut current_span, threads, tag); + let state = state.borrow(); + self.for_each(range, dcx, |stack, dcx, exposed_tags| { + stack.dealloc(tag, &state, dcx, exposed_tags) + })?; + Ok(()) + } +} + +/// Retagging/reborrowing. There is some policy in here, such as which permissions +/// to grant for which references, and when to add protectors. +impl<'mir: 'ecx, 'tcx: 'mir, 'ecx> EvalContextPrivExt<'mir, 'tcx, 'ecx> + for crate::MiriInterpCx<'mir, 'tcx> +{ +} +trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'mir, 'tcx> { + /// Returns the `AllocId` the reborrow was done in, if some actual borrow stack manipulation + /// happened. + fn reborrow( + &mut self, + place: &MPlaceTy<'tcx, Provenance>, + size: Size, + kind: RefKind, + retag_cause: RetagCause, // What caused this retag, for diagnostics only + new_tag: SbTag, + protect: bool, + ) -> InterpResult<'tcx, Option> { + let this = self.eval_context_mut(); + + // It is crucial that this gets called on all code paths, to ensure we track tag creation. + let log_creation = |this: &MiriInterpCx<'mir, 'tcx>, + loc: Option<(AllocId, Size, ProvenanceExtra)>| // alloc_id, base_offset, orig_tag + -> InterpResult<'tcx> { + let global = this.machine.stacked_borrows.as_ref().unwrap().borrow(); + if global.tracked_pointer_tags.contains(&new_tag) { + this.emit_diagnostic(NonHaltingDiagnostic::CreatedPointerTag( + new_tag.0, + loc.map(|(alloc_id, base_offset, _)| (alloc_id, alloc_range(base_offset, size))), + )); + } + drop(global); // don't hold that reference any longer than we have to + + let Some((alloc_id, base_offset, orig_tag)) = loc else { + return Ok(()) + }; + + let (_size, _align, alloc_kind) = this.get_alloc_info(alloc_id); + match alloc_kind { + AllocKind::LiveData => { + let current_span = &mut this.machine.current_span(); + // This should have alloc_extra data, but `get_alloc_extra` can still fail + // if converting this alloc_id from a global to a local one + // uncovers a non-supported `extern static`. + let extra = this.get_alloc_extra(alloc_id)?; + let mut stacked_borrows = extra + .stacked_borrows + .as_ref() + .expect("we should have Stacked Borrows data") + .borrow_mut(); + let threads = &this.machine.threads; + // Note that we create a *second* `DiagnosticCxBuilder` below for the actual retag. + // FIXME: can this be done cleaner? + let dcx = DiagnosticCxBuilder::retag( + current_span, + threads, + retag_cause, + new_tag, + orig_tag, + alloc_range(base_offset, size), + ); + let mut dcx = dcx.build(&mut stacked_borrows.history, base_offset); + dcx.log_creation(); + if protect { + dcx.log_protector(); + } + } + AllocKind::Function | AllocKind::VTable | AllocKind::Dead => { + // No stacked borrows on these allocations. + } + } + Ok(()) + }; + + if size == Size::ZERO { + trace!( + "reborrow of size 0: {} reference {:?} derived from {:?} (pointee {})", + kind, + new_tag, + place.ptr, + place.layout.ty, + ); + // Don't update any stacks for a zero-sized access; borrow stacks are per-byte and this + // touches no bytes so there is no stack to put this tag in. + // However, if the pointer for this operation points at a real allocation we still + // record where it was created so that we can issue a helpful diagnostic if there is an + // attempt to use it for a non-zero-sized access. + // Dangling slices are a common case here; it's valid to get their length but with raw + // pointer tagging for example all calls to get_unchecked on them are invalid. + if let Ok((alloc_id, base_offset, orig_tag)) = this.ptr_try_get_alloc_id(place.ptr) { + log_creation(this, Some((alloc_id, base_offset, orig_tag)))?; + return Ok(Some(alloc_id)); + } + // This pointer doesn't come with an AllocId. :shrug: + log_creation(this, None)?; + return Ok(None); + } + + let (alloc_id, base_offset, orig_tag) = this.ptr_get_alloc_id(place.ptr)?; + log_creation(this, Some((alloc_id, base_offset, orig_tag)))?; + + // Ensure we bail out if the pointer goes out-of-bounds (see miri#1050). + let (alloc_size, _) = this.get_live_alloc_size_and_align(alloc_id)?; + if base_offset + size > alloc_size { + throw_ub!(PointerOutOfBounds { + alloc_id, + alloc_size, + ptr_offset: this.machine_usize_to_isize(base_offset.bytes()), + ptr_size: size, + msg: CheckInAllocMsg::InboundsTest + }); + } + + trace!( + "reborrow: {} reference {:?} derived from {:?} (pointee {}): {:?}, size {}", + kind, + new_tag, + orig_tag, + place.layout.ty, + Pointer::new(alloc_id, base_offset), + size.bytes() + ); + + if protect { + // See comment in `Stack::item_popped` for why we store the tag twice. + this.frame_mut().extra.stacked_borrows.as_mut().unwrap().protected_tags.push(new_tag); + this.machine.stacked_borrows.as_mut().unwrap().get_mut().protected_tags.insert(new_tag); + } + + // Update the stacks. + // Make sure that raw pointers and mutable shared references are reborrowed "weak": + // There could be existing unique pointers reborrowed from them that should remain valid! + let perm = match kind { + RefKind::Unique { two_phase: false } + if place.layout.ty.is_unpin(this.tcx.at(DUMMY_SP), this.param_env()) => + { + // Only if the type is unpin do we actually enforce uniqueness + Permission::Unique + } + RefKind::Unique { .. } => { + // Two-phase references and !Unpin references are treated as SharedReadWrite + Permission::SharedReadWrite + } + RefKind::Raw { mutable: true } => Permission::SharedReadWrite, + RefKind::Shared | RefKind::Raw { mutable: false } => { + // Shared references and *const are a whole different kind of game, the + // permission is not uniform across the entire range! + // We need a frozen-sensitive reborrow. + // We have to use shared references to alloc/memory_extra here since + // `visit_freeze_sensitive` needs to access the global state. + let extra = this.get_alloc_extra(alloc_id)?; + let mut stacked_borrows = extra + .stacked_borrows + .as_ref() + .expect("we should have Stacked Borrows data") + .borrow_mut(); + // FIXME: can't share this with the current_span inside log_creation + let mut current_span = this.machine.current_span(); + this.visit_freeze_sensitive(place, size, |mut range, frozen| { + // Adjust range. + range.start += base_offset; + // We are only ever `SharedReadOnly` inside the frozen bits. + let perm = if frozen { + Permission::SharedReadOnly + } else { + Permission::SharedReadWrite + }; + let protected = if frozen { + protect + } else { + // We do not protect inside UnsafeCell. + // This fixes https://github.com/rust-lang/rust/issues/55005. + false + }; + let item = Item::new(new_tag, perm, protected); + let mut global = this.machine.stacked_borrows.as_ref().unwrap().borrow_mut(); + let dcx = DiagnosticCxBuilder::retag( + &mut current_span, // FIXME avoid this `clone` + &this.machine.threads, + retag_cause, + new_tag, + orig_tag, + alloc_range(base_offset, size), + ); + stacked_borrows.for_each(range, dcx, |stack, dcx, exposed_tags| { + stack.grant(orig_tag, item, &mut global, dcx, exposed_tags) + }) + })?; + return Ok(Some(alloc_id)); + } + }; + + // Here we can avoid `borrow()` calls because we have mutable references. + // Note that this asserts that the allocation is mutable -- but since we are creating a + // mutable pointer, that seems reasonable. + let (alloc_extra, machine) = this.get_alloc_extra_mut(alloc_id)?; + let mut stacked_borrows = alloc_extra + .stacked_borrows + .as_mut() + .expect("we should have Stacked Borrows data") + .borrow_mut(); + let item = Item::new(new_tag, perm, protect); + let range = alloc_range(base_offset, size); + let mut global = machine.stacked_borrows.as_ref().unwrap().borrow_mut(); + // FIXME: can't share this with the current_span inside log_creation + let current_span = &mut machine.current_span(); + let dcx = DiagnosticCxBuilder::retag( + current_span, + &machine.threads, + retag_cause, + new_tag, + orig_tag, + alloc_range(base_offset, size), + ); + stacked_borrows.for_each(range, dcx, |stack, dcx, exposed_tags| { + stack.grant(orig_tag, item, &mut global, dcx, exposed_tags) + })?; + + Ok(Some(alloc_id)) + } + + /// Retags an indidual pointer, returning the retagged version. + /// `mutbl` can be `None` to make this a raw pointer. + fn retag_reference( + &mut self, + val: &ImmTy<'tcx, Provenance>, + kind: RefKind, + retag_cause: RetagCause, // What caused this retag, for diagnostics only + protect: bool, + ) -> InterpResult<'tcx, ImmTy<'tcx, Provenance>> { + let this = self.eval_context_mut(); + // We want a place for where the ptr *points to*, so we get one. + let place = this.ref_to_mplace(val)?; + let size = this.size_and_align_of_mplace(&place)?.map(|(size, _)| size); + // FIXME: If we cannot determine the size (because the unsized tail is an `extern type`), + // bail out -- we cannot reasonably figure out which memory range to reborrow. + // See https://github.com/rust-lang/unsafe-code-guidelines/issues/276. + let size = match size { + Some(size) => size, + None => return Ok(val.clone()), + }; + + // Compute new borrow. + let new_tag = this.machine.stacked_borrows.as_mut().unwrap().get_mut().new_ptr(); + + // Reborrow. + let alloc_id = this.reborrow(&place, size, kind, retag_cause, new_tag, protect)?; + + // Adjust pointer. + let new_place = place.map_provenance(|p| { + p.map(|prov| { + match alloc_id { + Some(alloc_id) => { + // If `reborrow` could figure out the AllocId of this ptr, hard-code it into the new one. + // Even if we started out with a wildcard, this newly retagged pointer is tied to that allocation. + Provenance::Concrete { alloc_id, sb: new_tag } + } + None => { + // Looks like this has to stay a wildcard pointer. + assert!(matches!(prov, Provenance::Wildcard)); + Provenance::Wildcard + } + } + }) + }); + + // Return new pointer. + Ok(ImmTy::from_immediate(new_place.to_ref(this), val.layout)) + } +} + +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + fn retag(&mut self, kind: RetagKind, place: &PlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + let retag_fields = this.machine.stacked_borrows.as_mut().unwrap().get_mut().retag_fields; + let retag_cause = match kind { + RetagKind::TwoPhase { .. } => RetagCause::TwoPhase, + RetagKind::FnEntry => RetagCause::FnEntry, + RetagKind::Raw | RetagKind::Default => RetagCause::Normal, + }; + let mut visitor = RetagVisitor { ecx: this, kind, retag_cause, retag_fields }; + return visitor.visit_value(place); + + // Determine mutability and whether to add a protector. + // Cannot use `builtin_deref` because that reports *immutable* for `Box`, + // making it useless. + fn qualify(ty: Ty<'_>, kind: RetagKind) -> Option<(RefKind, bool)> { + match ty.kind() { + // References are simple. + ty::Ref(_, _, Mutability::Mut) => + Some(( + RefKind::Unique { two_phase: kind == RetagKind::TwoPhase }, + kind == RetagKind::FnEntry, + )), + ty::Ref(_, _, Mutability::Not) => + Some((RefKind::Shared, kind == RetagKind::FnEntry)), + // Raw pointers need to be enabled. + ty::RawPtr(tym) if kind == RetagKind::Raw => + Some((RefKind::Raw { mutable: tym.mutbl == Mutability::Mut }, false)), + // Boxes are handled separately due to that allocator situation, + // see the visitor below. + _ => None, + } + } + + // The actual visitor. + struct RetagVisitor<'ecx, 'mir, 'tcx> { + ecx: &'ecx mut MiriInterpCx<'mir, 'tcx>, + kind: RetagKind, + retag_cause: RetagCause, + retag_fields: bool, + } + impl<'ecx, 'mir, 'tcx> RetagVisitor<'ecx, 'mir, 'tcx> { + #[inline(always)] // yes this helps in our benchmarks + fn retag_place( + &mut self, + place: &PlaceTy<'tcx, Provenance>, + ref_kind: RefKind, + retag_cause: RetagCause, + protector: bool, + ) -> InterpResult<'tcx> { + let val = self.ecx.read_immediate(&self.ecx.place_to_op(place)?)?; + let val = self.ecx.retag_reference(&val, ref_kind, retag_cause, protector)?; + self.ecx.write_immediate(*val, place)?; + Ok(()) + } + } + impl<'ecx, 'mir, 'tcx> MutValueVisitor<'mir, 'tcx, MiriMachine<'mir, 'tcx>> + for RetagVisitor<'ecx, 'mir, 'tcx> + { + type V = PlaceTy<'tcx, Provenance>; + + #[inline(always)] + fn ecx(&mut self) -> &mut MiriInterpCx<'mir, 'tcx> { + self.ecx + } + + fn visit_box(&mut self, place: &PlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> { + // Boxes do not get a protector: protectors reflect that references outlive the call + // they were passed in to; that's just not the case for boxes. + self.retag_place( + place, + RefKind::Unique { two_phase: false }, + self.retag_cause, + /*protector*/ false, + ) + } + + fn visit_value(&mut self, place: &PlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> { + // If this place is smaller than a pointer, we know that it can't contain any + // pointers we need to retag, so we can stop recursion early. + // This optimization is crucial for ZSTs, because they can contain way more fields + // than we can ever visit. + if !place.layout.is_unsized() && place.layout.size < self.ecx.pointer_size() { + return Ok(()); + } + + if let Some((ref_kind, protector)) = qualify(place.layout.ty, self.kind) { + self.retag_place(place, ref_kind, self.retag_cause, protector)?; + } else if matches!(place.layout.ty.kind(), ty::RawPtr(..)) { + // Wide raw pointers *do* have fields and their types are strange. + // vtables have a type like `&[*const (); 3]` or so! + // Do *not* recurse into them. + // (No need to worry about wide references, those always "qualify". And Boxes + // are handles specially by the visitor anyway.) + } else if self.retag_fields + || place.layout.ty.ty_adt_def().is_some_and(|adt| adt.is_box()) + { + // Recurse deeper. Need to always recurse for `Box` to even hit `visit_box`. + // (Yes this means we technically also recursively retag the allocator itself + // even if field retagging is not enabled. *shrug*) + self.walk_value(place)?; + } + Ok(()) + } + } + } + + /// After a stack frame got pushed, retag the return place so that we are sure + /// it does not alias with anything. + /// + /// This is a HACK because there is nothing in MIR that would make the retag + /// explicit. Also see . + fn retag_return_place(&mut self) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + let return_place = &this.frame().return_place; + if return_place.layout.is_zst() { + // There may not be any memory here, nothing to do. + return Ok(()); + } + // We need this to be in-memory to use tagged pointers. + let return_place = this.force_allocation(&return_place.clone())?; + + // We have to turn the place into a pointer to use the existing code. + // (The pointer type does not matter, so we use a raw pointer.) + let ptr_layout = this.layout_of(this.tcx.mk_mut_ptr(return_place.layout.ty))?; + let val = ImmTy::from_immediate(return_place.to_ref(this), ptr_layout); + // Reborrow it. + let val = this.retag_reference( + &val, + RefKind::Unique { two_phase: false }, + RetagCause::FnReturn, + /*protector*/ true, + )?; + // And use reborrowed pointer for return place. + let return_place = this.ref_to_mplace(&val)?; + this.frame_mut().return_place = return_place.into(); + + Ok(()) + } + + /// Mark the given tag as exposed. It was found on a pointer with the given AllocId. + fn expose_tag(&mut self, alloc_id: AllocId, tag: SbTag) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + + // Function pointers and dead objects don't have an alloc_extra so we ignore them. + // This is okay because accessing them is UB anyway, no need for any Stacked Borrows checks. + // NOT using `get_alloc_extra_mut` since this might be a read-only allocation! + let (_size, _align, kind) = this.get_alloc_info(alloc_id); + match kind { + AllocKind::LiveData => { + // This should have alloc_extra data, but `get_alloc_extra` can still fail + // if converting this alloc_id from a global to a local one + // uncovers a non-supported `extern static`. + let alloc_extra = this.get_alloc_extra(alloc_id)?; + trace!("Stacked Borrows tag {tag:?} exposed in {alloc_id:?}"); + alloc_extra.stacked_borrows.as_ref().unwrap().borrow_mut().exposed_tags.insert(tag); + } + AllocKind::Function | AllocKind::VTable | AllocKind::Dead => { + // No stacked borrows on these allocations. + } + } + Ok(()) + } +} diff --git a/src/tools/miri/src/stacked_borrows/stack.rs b/src/tools/miri/src/stacked_borrows/stack.rs new file mode 100644 index 0000000000000..494ea08b56e48 --- /dev/null +++ b/src/tools/miri/src/stacked_borrows/stack.rs @@ -0,0 +1,462 @@ +#[cfg(feature = "stack-cache")] +use std::ops::Range; + +use rustc_data_structures::fx::FxHashSet; + +use crate::stacked_borrows::{AccessKind, Item, Permission, SbTag}; +use crate::ProvenanceExtra; + +/// Exactly what cache size we should use is a difficult tradeoff. There will always be some +/// workload which has a `SbTag` working set which exceeds the size of the cache, and ends up +/// falling back to linear searches of the borrow stack very often. +/// The cost of making this value too large is that the loop in `Stack::insert` which ensures the +/// entries in the cache stay correct after an insert becomes expensive. +#[cfg(feature = "stack-cache")] +const CACHE_LEN: usize = 32; + +/// Extra per-location state. +#[derive(Clone, Debug)] +pub struct Stack { + /// Used *mostly* as a stack; never empty. + /// Invariants: + /// * Above a `SharedReadOnly` there can only be more `SharedReadOnly`. + /// * Except for `Untagged`, no tag occurs in the stack more than once. + borrows: Vec, + /// If this is `Some(id)`, then the actual current stack is unknown. This can happen when + /// wildcard pointers are used to access this location. What we do know is that `borrows` are at + /// the top of the stack, and below it are arbitrarily many items whose `tag` is strictly less + /// than `id`. + /// When the bottom is unknown, `borrows` always has a `SharedReadOnly` or `Unique` at the bottom; + /// we never have the unknown-to-known boundary in an SRW group. + unknown_bottom: Option, + + /// A small LRU cache of searches of the borrow stack. + #[cfg(feature = "stack-cache")] + cache: StackCache, + /// On a read, we need to disable all `Unique` above the granting item. We can avoid most of + /// this scan by keeping track of the region of the borrow stack that may contain `Unique`s. + #[cfg(feature = "stack-cache")] + unique_range: Range, +} + +impl Stack { + pub fn retain(&mut self, tags: &FxHashSet) { + let mut first_removed = None; + + let mut read_idx = 1; + let mut write_idx = 1; + while read_idx < self.borrows.len() { + let left = self.borrows[read_idx - 1]; + let this = self.borrows[read_idx]; + let should_keep = match this.perm() { + // SharedReadWrite is the simplest case, if it's unreachable we can just remove it. + Permission::SharedReadWrite => tags.contains(&this.tag()), + // Only retain a Disabled tag if it is terminating a SharedReadWrite block. + Permission::Disabled => left.perm() == Permission::SharedReadWrite, + // Unique and SharedReadOnly can terminate a SharedReadWrite block, so only remove + // them if they are both unreachable and not directly after a SharedReadWrite. + Permission::Unique | Permission::SharedReadOnly => + left.perm() == Permission::SharedReadWrite || tags.contains(&this.tag()), + }; + + if should_keep { + if read_idx != write_idx { + self.borrows[write_idx] = self.borrows[read_idx]; + } + write_idx += 1; + } else if first_removed.is_none() { + first_removed = Some(read_idx); + } + + read_idx += 1; + } + self.borrows.truncate(write_idx); + + #[cfg(not(feature = "stack-cache"))] + drop(first_removed); // This is only needed for the stack-cache + + #[cfg(feature = "stack-cache")] + if let Some(first_removed) = first_removed { + // Either end of unique_range may have shifted, all we really know is that we can't + // have introduced a new Unique. + if !self.unique_range.is_empty() { + self.unique_range = 0..self.len(); + } + + // Replace any Items which have been collected with the base item, a known-good value. + for i in 0..CACHE_LEN { + if self.cache.idx[i] >= first_removed { + self.cache.items[i] = self.borrows[0]; + self.cache.idx[i] = 0; + } + } + } + } +} + +/// A very small cache of searches of a borrow stack, mapping `Item`s to their position in said stack. +/// +/// It may seem like maintaining this cache is a waste for small stacks, but +/// (a) iterating over small fixed-size arrays is super fast, and (b) empirically this helps *a lot*, +/// probably because runtime is dominated by large stacks. +#[cfg(feature = "stack-cache")] +#[derive(Clone, Debug)] +struct StackCache { + items: [Item; CACHE_LEN], // Hot in find_granting + idx: [usize; CACHE_LEN], // Hot in grant +} + +#[cfg(feature = "stack-cache")] +impl StackCache { + /// When a tag is used, we call this function to add or refresh it in the cache. + /// + /// We use the position in the cache to represent how recently a tag was used; the first position + /// is the most recently used tag. So an add shifts every element towards the end, and inserts + /// the new element at the start. We lose the last element. + /// This strategy is effective at keeping the most-accessed items in the cache, but it costs a + /// linear shift across the entire cache when we add a new tag. + fn add(&mut self, idx: usize, item: Item) { + self.items.copy_within(0..CACHE_LEN - 1, 1); + self.items[0] = item; + self.idx.copy_within(0..CACHE_LEN - 1, 1); + self.idx[0] = idx; + } +} + +impl PartialEq for Stack { + fn eq(&self, other: &Self) -> bool { + // All the semantics of Stack are in self.borrows, everything else is caching + self.borrows == other.borrows + } +} + +impl Eq for Stack {} + +impl<'tcx> Stack { + /// Panics if any of the caching mechanisms have broken, + /// - The StackCache indices don't refer to the parallel items, + /// - There are no Unique items outside of first_unique..last_unique + #[cfg(all(feature = "stack-cache", debug_assertions))] + fn verify_cache_consistency(&self) { + // Only a full cache needs to be valid. Also see the comments in find_granting_cache + // and set_unknown_bottom. + if self.borrows.len() >= CACHE_LEN { + for (tag, stack_idx) in self.cache.items.iter().zip(self.cache.idx.iter()) { + assert_eq!(self.borrows[*stack_idx], *tag); + } + } + + // Check that all Unique items fall within unique_range. + for (idx, item) in self.borrows.iter().enumerate() { + if item.perm() == Permission::Unique { + assert!( + self.unique_range.contains(&idx), + "{:?} {:?}", + self.unique_range, + self.borrows + ); + } + } + + // Check that the unique_range is a valid index into the borrow stack. + // This asserts that the unique_range's start <= end. + let _uniques = &self.borrows[self.unique_range.clone()]; + + // We cannot assert that the unique range is precise. + // Both ends may shift around when `Stack::retain` is called. Additionally, + // when we pop items within the unique range, setting the end of the range precisely + // requires doing a linear search of the borrow stack, which is exactly the kind of + // operation that all this caching exists to avoid. + } + + /// Find the item granting the given kind of access to the given tag, and return where + /// it is on the stack. For wildcard tags, the given index is approximate, but if *no* + /// index is given it means the match was *not* in the known part of the stack. + /// `Ok(None)` indicates it matched the "unknown" part of the stack. + /// `Err` indicates it was not found. + pub(super) fn find_granting( + &mut self, + access: AccessKind, + tag: ProvenanceExtra, + exposed_tags: &FxHashSet, + ) -> Result, ()> { + #[cfg(all(feature = "stack-cache", debug_assertions))] + self.verify_cache_consistency(); + + let ProvenanceExtra::Concrete(tag) = tag else { + // Handle the wildcard case. + // Go search the stack for an exposed tag. + if let Some(idx) = + self.borrows + .iter() + .enumerate() // we also need to know *where* in the stack + .rev() // search top-to-bottom + .find_map(|(idx, item)| { + // If the item fits and *might* be this wildcard, use it. + if item.perm().grants(access) && exposed_tags.contains(&item.tag()) { + Some(idx) + } else { + None + } + }) + { + return Ok(Some(idx)); + } + // If we couldn't find it in the stack, check the unknown bottom. + return if self.unknown_bottom.is_some() { Ok(None) } else { Err(()) }; + }; + + if let Some(idx) = self.find_granting_tagged(access, tag) { + return Ok(Some(idx)); + } + + // Couldn't find it in the stack; but if there is an unknown bottom it might be there. + let found = self.unknown_bottom.is_some_and(|&unknown_limit| { + tag.0 < unknown_limit.0 // unknown_limit is an upper bound for what can be in the unknown bottom. + }); + if found { Ok(None) } else { Err(()) } + } + + fn find_granting_tagged(&mut self, access: AccessKind, tag: SbTag) -> Option { + #[cfg(feature = "stack-cache")] + if let Some(idx) = self.find_granting_cache(access, tag) { + return Some(idx); + } + + // If we didn't find the tag in the cache, fall back to a linear search of the + // whole stack, and add the tag to the cache. + for (stack_idx, item) in self.borrows.iter().enumerate().rev() { + if tag == item.tag() && item.perm().grants(access) { + #[cfg(feature = "stack-cache")] + self.cache.add(stack_idx, *item); + return Some(stack_idx); + } + } + None + } + + #[cfg(feature = "stack-cache")] + fn find_granting_cache(&mut self, access: AccessKind, tag: SbTag) -> Option { + // This looks like a common-sense optimization; we're going to do a linear search of the + // cache or the borrow stack to scan the shorter of the two. This optimization is miniscule + // and this check actually ensures we do not access an invalid cache. + // When a stack is created and when items are removed from the top of the borrow stack, we + // need some valid value to populate the cache. In both cases, we try to use the bottom + // item. But when the stack is cleared in `set_unknown_bottom` there is nothing we could + // place in the cache that is correct. But due to the way we populate the cache in + // `StackCache::add`, we know that when the borrow stack has grown larger than the cache, + // every slot in the cache is valid. + if self.borrows.len() <= CACHE_LEN { + return None; + } + // Search the cache for the tag we're looking up + let cache_idx = self.cache.items.iter().position(|t| t.tag() == tag)?; + let stack_idx = self.cache.idx[cache_idx]; + // If we found the tag, look up its position in the stack to see if it grants + // the required permission + if self.cache.items[cache_idx].perm().grants(access) { + // If it does, and it's not already in the most-recently-used position, re-insert it at + // the most-recently-used position. This technically reduces the efficiency of the + // cache by duplicating elements, but current benchmarks do not seem to benefit from + // avoiding this duplication. + // But if the tag is in position 1, avoiding the duplicating add is trivial. + // If it does, and it's not already in the most-recently-used position, move it there. + // Except if the tag is in position 1, this is equivalent to just a swap, so do that. + if cache_idx == 1 { + self.cache.items.swap(0, 1); + self.cache.idx.swap(0, 1); + } else if cache_idx > 1 { + self.cache.add(stack_idx, self.cache.items[cache_idx]); + } + Some(stack_idx) + } else { + // Tag is in the cache, but it doesn't grant the required permission + None + } + } + + pub fn insert(&mut self, new_idx: usize, new: Item) { + self.borrows.insert(new_idx, new); + + #[cfg(feature = "stack-cache")] + self.insert_cache(new_idx, new); + } + + #[cfg(feature = "stack-cache")] + fn insert_cache(&mut self, new_idx: usize, new: Item) { + // Adjust the possibly-unique range if an insert occurs before or within it + if self.unique_range.start >= new_idx { + self.unique_range.start += 1; + } + if self.unique_range.end >= new_idx { + self.unique_range.end += 1; + } + if new.perm() == Permission::Unique { + // If this is the only Unique, set the range to contain just the new item. + if self.unique_range.is_empty() { + self.unique_range = new_idx..new_idx + 1; + } else { + // We already have other Unique items, expand the range to include the new item + self.unique_range.start = self.unique_range.start.min(new_idx); + self.unique_range.end = self.unique_range.end.max(new_idx + 1); + } + } + + // The above insert changes the meaning of every index in the cache >= new_idx, so now + // we need to find every one of those indexes and increment it. + // But if the insert is at the end (equivalent to a push), we can skip this step because + // it didn't change the position of any other items. + if new_idx != self.borrows.len() - 1 { + for idx in &mut self.cache.idx { + if *idx >= new_idx { + *idx += 1; + } + } + } + + // This primes the cache for the next access, which is almost always the just-added tag. + self.cache.add(new_idx, new); + + #[cfg(debug_assertions)] + self.verify_cache_consistency(); + } + + /// Construct a new `Stack` using the passed `Item` as the base tag. + pub fn new(item: Item) -> Self { + Stack { + borrows: vec![item], + unknown_bottom: None, + #[cfg(feature = "stack-cache")] + cache: StackCache { idx: [0; CACHE_LEN], items: [item; CACHE_LEN] }, + #[cfg(feature = "stack-cache")] + unique_range: if item.perm() == Permission::Unique { 0..1 } else { 0..0 }, + } + } + + pub fn get(&self, idx: usize) -> Option { + self.borrows.get(idx).cloned() + } + + #[allow(clippy::len_without_is_empty)] // Stacks are never empty + pub fn len(&self) -> usize { + self.borrows.len() + } + + pub fn unknown_bottom(&self) -> Option { + self.unknown_bottom + } + + pub fn set_unknown_bottom(&mut self, tag: SbTag) { + // We clear the borrow stack but the lookup cache doesn't support clearing per se. Instead, + // there is a check explained in `find_granting_cache` which protects against accessing the + // cache when it has been cleared and not yet refilled. + self.borrows.clear(); + self.unknown_bottom = Some(tag); + #[cfg(feature = "stack-cache")] + { + self.unique_range = 0..0; + } + } + + /// Find all `Unique` elements in this borrow stack above `granting_idx`, pass a copy of them + /// to the `visitor`, then set their `Permission` to `Disabled`. + pub fn disable_uniques_starting_at crate::InterpResult<'tcx>>( + &mut self, + disable_start: usize, + mut visitor: V, + ) -> crate::InterpResult<'tcx> { + #[cfg(feature = "stack-cache")] + let unique_range = self.unique_range.clone(); + #[cfg(not(feature = "stack-cache"))] + let unique_range = 0..self.len(); + + if disable_start <= unique_range.end { + let lower = unique_range.start.max(disable_start); + let upper = unique_range.end; + for item in &mut self.borrows[lower..upper] { + if item.perm() == Permission::Unique { + log::trace!("access: disabling item {:?}", item); + visitor(*item)?; + item.set_permission(Permission::Disabled); + // Also update all copies of this item in the cache. + #[cfg(feature = "stack-cache")] + for it in &mut self.cache.items { + if it.tag() == item.tag() { + it.set_permission(Permission::Disabled); + } + } + } + } + } + + #[cfg(feature = "stack-cache")] + if disable_start <= self.unique_range.start { + // We disabled all Unique items + self.unique_range.start = 0; + self.unique_range.end = 0; + } else { + // Truncate the range to only include items up to the index that we started disabling + // at. + self.unique_range.end = self.unique_range.end.min(disable_start); + } + + #[cfg(all(feature = "stack-cache", debug_assertions))] + self.verify_cache_consistency(); + + Ok(()) + } + + /// Produces an iterator which iterates over `range` in reverse, and when dropped removes that + /// range of `Item`s from this `Stack`. + pub fn pop_items_after crate::InterpResult<'tcx>>( + &mut self, + start: usize, + mut visitor: V, + ) -> crate::InterpResult<'tcx> { + while self.borrows.len() > start { + let item = self.borrows.pop().unwrap(); + visitor(item)?; + } + + #[cfg(feature = "stack-cache")] + if !self.borrows.is_empty() { + // After we remove from the borrow stack, every aspect of our caching may be invalid, but it is + // also possible that the whole cache is still valid. So we call this method to repair what + // aspects of the cache are now invalid, instead of resetting the whole thing to a trivially + // valid default state. + let base_tag = self.borrows[0]; + let mut removed = 0; + let mut cursor = 0; + // Remove invalid entries from the cache by rotating them to the end of the cache, then + // keep track of how many invalid elements there are and overwrite them with the base tag. + // The base tag here serves as a harmless default value. + for _ in 0..CACHE_LEN - 1 { + if self.cache.idx[cursor] >= start { + self.cache.idx[cursor..CACHE_LEN - removed].rotate_left(1); + self.cache.items[cursor..CACHE_LEN - removed].rotate_left(1); + removed += 1; + } else { + cursor += 1; + } + } + for i in CACHE_LEN - removed - 1..CACHE_LEN { + self.cache.idx[i] = 0; + self.cache.items[i] = base_tag; + } + + if start <= self.unique_range.start { + // We removed all the Unique items + self.unique_range = 0..0; + } else { + // Ensure the range doesn't extend past the new top of the stack + self.unique_range.end = self.unique_range.end.min(start); + } + } else { + self.unique_range = 0..0; + } + + #[cfg(all(feature = "stack-cache", debug_assertions))] + self.verify_cache_consistency(); + Ok(()) + } +} diff --git a/src/tools/miri/src/tag_gc.rs b/src/tools/miri/src/tag_gc.rs new file mode 100644 index 0000000000000..e20a86711478a --- /dev/null +++ b/src/tools/miri/src/tag_gc.rs @@ -0,0 +1,117 @@ +use crate::*; +use rustc_data_structures::fx::FxHashSet; + +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +pub trait EvalContextExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> { + fn garbage_collect_tags(&mut self) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + // No reason to do anything at all if stacked borrows is off. + if this.machine.stacked_borrows.is_none() { + return Ok(()); + } + + let mut tags = FxHashSet::default(); + + for thread in this.machine.threads.iter() { + if let Some(Scalar::Ptr( + Pointer { provenance: Provenance::Concrete { sb, .. }, .. }, + _, + )) = thread.panic_payload + { + tags.insert(sb); + } + } + + self.find_tags_in_tls(&mut tags); + self.find_tags_in_memory(&mut tags); + self.find_tags_in_locals(&mut tags)?; + + self.remove_unreachable_tags(tags); + + Ok(()) + } + + fn find_tags_in_tls(&mut self, tags: &mut FxHashSet) { + let this = self.eval_context_mut(); + this.machine.tls.iter(|scalar| { + if let Scalar::Ptr(Pointer { provenance: Provenance::Concrete { sb, .. }, .. }, _) = + scalar + { + tags.insert(*sb); + } + }); + } + + fn find_tags_in_memory(&mut self, tags: &mut FxHashSet) { + let this = self.eval_context_mut(); + this.memory.alloc_map().iter(|it| { + for (_id, (_kind, alloc)) in it { + for (_size, prov) in alloc.provenance().iter() { + if let Provenance::Concrete { sb, .. } = prov { + tags.insert(*sb); + } + } + } + }); + } + + fn find_tags_in_locals(&mut self, tags: &mut FxHashSet) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + for frame in this.machine.threads.all_stacks().flatten() { + // Handle the return place of each frame + if let Ok(return_place) = frame.return_place.try_as_mplace() { + if let Some(Provenance::Concrete { sb, .. }) = return_place.ptr.provenance { + tags.insert(sb); + } + } + + for local in frame.locals.iter() { + let LocalValue::Live(value) = local.value else { + continue; + }; + match value { + Operand::Immediate(Immediate::Scalar(Scalar::Ptr(ptr, _))) => + if let Provenance::Concrete { sb, .. } = ptr.provenance { + tags.insert(sb); + }, + Operand::Immediate(Immediate::ScalarPair(s1, s2)) => { + if let Scalar::Ptr(ptr, _) = s1 { + if let Provenance::Concrete { sb, .. } = ptr.provenance { + tags.insert(sb); + } + } + if let Scalar::Ptr(ptr, _) = s2 { + if let Provenance::Concrete { sb, .. } = ptr.provenance { + tags.insert(sb); + } + } + } + Operand::Indirect(MemPlace { ptr, .. }) => { + if let Some(Provenance::Concrete { sb, .. }) = ptr.provenance { + tags.insert(sb); + } + } + Operand::Immediate(Immediate::Uninit) + | Operand::Immediate(Immediate::Scalar(Scalar::Int(_))) => {} + } + } + } + + Ok(()) + } + + fn remove_unreachable_tags(&mut self, tags: FxHashSet) { + let this = self.eval_context_mut(); + this.memory.alloc_map().iter(|it| { + for (_id, (_kind, alloc)) in it { + alloc + .extra + .stacked_borrows + .as_ref() + .unwrap() + .borrow_mut() + .remove_unreachable_tags(&tags); + } + }); + } +} diff --git a/src/tools/miri/test-cargo-miri/.gitignore b/src/tools/miri/test-cargo-miri/.gitignore new file mode 100644 index 0000000000000..af5854e0c3fd9 --- /dev/null +++ b/src/tools/miri/test-cargo-miri/.gitignore @@ -0,0 +1,4 @@ +*.real +custom-run +custom-test +config-cli diff --git a/src/tools/miri/test-cargo-miri/Cargo.lock b/src/tools/miri/test-cargo-miri/Cargo.lock new file mode 100644 index 0000000000000..a297dd27dbc94 --- /dev/null +++ b/src/tools/miri/test-cargo-miri/Cargo.lock @@ -0,0 +1,135 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "byteorder" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc10e8cc6b2580fda3f36eb6dc5316657f812a3df879a44a66fc9f0fdbc4855" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cargo-miri-test" +version = "0.1.0" +dependencies = [ + "autocfg", + "byteorder 0.5.3", + "byteorder 1.4.3", + "cdylib", + "exported_symbol", + "issue_1567", + "issue_1691", + "issue_1705", + "issue_1760", + "issue_rust_86261", + "serde_derive", +] + +[[package]] +name = "cdylib" +version = "0.1.0" +dependencies = [ + "byteorder 1.4.3", +] + +[[package]] +name = "exported_symbol" +version = "0.1.0" +dependencies = [ + "exported_symbol_dep", +] + +[[package]] +name = "exported_symbol_dep" +version = "0.1.0" + +[[package]] +name = "issue_1567" +version = "0.1.0" +dependencies = [ + "byteorder 1.4.3", +] + +[[package]] +name = "issue_1691" +version = "0.1.0" + +[[package]] +name = "issue_1705" +version = "0.1.0" +dependencies = [ + "byteorder 1.4.3", +] + +[[package]] +name = "issue_1760" +version = "0.1.0" + +[[package]] +name = "issue_rust_86261" +version = "0.1.0" + +[[package]] +name = "proc-macro2" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "serde_derive" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "subcrate" +version = "0.1.0" +dependencies = [ + "byteorder 1.4.3", +] + +[[package]] +name = "syn" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" diff --git a/src/tools/miri/test-cargo-miri/Cargo.toml b/src/tools/miri/test-cargo-miri/Cargo.toml new file mode 100644 index 0000000000000..5d9e5d143b3b3 --- /dev/null +++ b/src/tools/miri/test-cargo-miri/Cargo.toml @@ -0,0 +1,32 @@ +[workspace] +members = ["subcrate", "issue-1567", "exported-symbol-dep"] + +[package] +name = "cargo-miri-test" +version = "0.1.0" +authors = ["Miri Team"] +edition = "2018" + +[dependencies] +byteorder = "1.0" +cdylib = { path = "cdylib" } +exported_symbol = { path = "exported-symbol" } +issue_1567 = { path = "issue-1567" } +issue_1691 = { path = "issue-1691" } +issue_1705 = { path = "issue-1705" } +issue_1760 = { path = "issue-1760" } +issue_rust_86261 = { path = "issue-rust-86261" } + +[dev-dependencies] +byteorder_2 = { package = "byteorder", version = "0.5" } # to test dev-dependencies behave as expected, with renaming +serde_derive = "1.0" # not actually used, but exercises some unique code path (`--extern` .so file) + +[build-dependencies] +autocfg = "1" + +[lib] +test = false # test that this is respected (will show in the output) + +[[test]] +name = "main" +harness = false diff --git a/src/tools/miri/test-cargo-miri/build.rs b/src/tools/miri/test-cargo-miri/build.rs new file mode 100644 index 0000000000000..6c1f4d80d3392 --- /dev/null +++ b/src/tools/miri/test-cargo-miri/build.rs @@ -0,0 +1,40 @@ +use std::env; + +#[cfg(miri)] +compile_error!("`miri` cfg should not be set in build script"); + +fn not_in_miri() -> i32 { + // Inline assembly definitely does not work in Miri. + let mut dummy = 42; + unsafe { + std::arch::asm!("/* {} */", in(reg) &mut dummy); + } + return dummy; +} + +fn main() { + not_in_miri(); + // Cargo calls `miri --print=cfg` to populate the `CARGO_CFG_*` env vars. + // Make sure that the "miri" flag is set. + assert!(env::var_os("CARGO_CFG_MIRI").is_some(), "cargo failed to tell us about `--cfg miri`"); + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-env-changed=MIRITESTVAR"); + println!("cargo:rustc-env=MIRITESTVAR=testval"); + + // Test that autocfg works. This invokes RUSTC. + let a = autocfg::new(); + assert!(a.probe_sysroot_crate("std")); + assert!(!a.probe_sysroot_crate("doesnotexist")); + assert!(a.probe_rustc_version(1, 0)); + assert!(!a.probe_rustc_version(2, 0)); + assert!(a.probe_type("i128")); + assert!(!a.probe_type("doesnotexist")); + assert!(a.probe_trait("Send")); + assert!(!a.probe_trait("doesnotexist")); + assert!(a.probe_path("std::num")); + assert!(!a.probe_path("doesnotexist")); + assert!(a.probe_constant("i32::MAX")); + assert!(!a.probe_constant("doesnotexist")); + assert!(a.probe_expression("Box::new(0)")); + assert!(!a.probe_expression("doesnotexist")); +} diff --git a/src/tools/miri/test-cargo-miri/cdylib/Cargo.toml b/src/tools/miri/test-cargo-miri/cdylib/Cargo.toml new file mode 100644 index 0000000000000..527602e0a888f --- /dev/null +++ b/src/tools/miri/test-cargo-miri/cdylib/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "cdylib" +version = "0.1.0" +authors = ["Miri Team"] +edition = "2018" + +[lib] +# cargo-miri used to handle `cdylib` crate-type specially (https://github.com/rust-lang/miri/pull/1577). +crate-type = ["cdylib"] + +[dependencies] +byteorder = "1.0" # to test dependencies of sub-crates diff --git a/src/tools/miri/test-cargo-miri/cdylib/src/lib.rs b/src/tools/miri/test-cargo-miri/cdylib/src/lib.rs new file mode 100644 index 0000000000000..e47e588251e4e --- /dev/null +++ b/src/tools/miri/test-cargo-miri/cdylib/src/lib.rs @@ -0,0 +1,6 @@ +use byteorder::{BigEndian, ByteOrder}; + +#[no_mangle] +extern "C" fn use_the_dependency() { + let _n = ::read_u64(&[1, 2, 3, 4, 5, 6, 7, 8]); +} diff --git a/src/tools/miri/test-cargo-miri/exported-symbol-dep/Cargo.toml b/src/tools/miri/test-cargo-miri/exported-symbol-dep/Cargo.toml new file mode 100644 index 0000000000000..00c41172c3af2 --- /dev/null +++ b/src/tools/miri/test-cargo-miri/exported-symbol-dep/Cargo.toml @@ -0,0 +1,5 @@ +[package] +name = "exported_symbol_dep" +version = "0.1.0" +authors = ["Miri Team"] +edition = "2018" diff --git a/src/tools/miri/test-cargo-miri/exported-symbol-dep/src/lib.rs b/src/tools/miri/test-cargo-miri/exported-symbol-dep/src/lib.rs new file mode 100644 index 0000000000000..5b8a314ae7324 --- /dev/null +++ b/src/tools/miri/test-cargo-miri/exported-symbol-dep/src/lib.rs @@ -0,0 +1,13 @@ +#[no_mangle] +fn exported_symbol() -> i32 { + 123456 +} + +struct AssocFn; + +impl AssocFn { + #[no_mangle] + fn assoc_fn_as_exported_symbol() -> i32 { + -123456 + } +} diff --git a/src/tools/miri/test-cargo-miri/exported-symbol/Cargo.toml b/src/tools/miri/test-cargo-miri/exported-symbol/Cargo.toml new file mode 100644 index 0000000000000..7c01be1a85f9c --- /dev/null +++ b/src/tools/miri/test-cargo-miri/exported-symbol/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "exported_symbol" +version = "0.1.0" +authors = ["Miri Team"] +edition = "2018" + +[dependencies] +# This will become a transitive dependency of doctests in `test-cargo-miri/src/lib.rs`, +# and the purpose of the test is to make sure Miri can find a `#[no_mangle]` function in a +# transitive dependency like `exported_symbol_dep`. +exported_symbol_dep = { path = "../exported-symbol-dep" } diff --git a/src/tools/miri/test-cargo-miri/exported-symbol/src/lib.rs b/src/tools/miri/test-cargo-miri/exported-symbol/src/lib.rs new file mode 100644 index 0000000000000..de55eb2a1a5a0 --- /dev/null +++ b/src/tools/miri/test-cargo-miri/exported-symbol/src/lib.rs @@ -0,0 +1 @@ +extern crate exported_symbol_dep; diff --git a/src/tools/miri/test-cargo-miri/issue-1567/Cargo.toml b/src/tools/miri/test-cargo-miri/issue-1567/Cargo.toml new file mode 100644 index 0000000000000..6a6e09036a01d --- /dev/null +++ b/src/tools/miri/test-cargo-miri/issue-1567/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "issue_1567" +version = "0.1.0" +authors = ["Miri Team"] +edition = "2018" + +[lib] +# Regression test for https://github.com/rust-lang/miri/issues/1567: crate must have this crate-type set. +# It must also depend on some other crate and use that dependency (we use byteorder). +crate-type = ["cdylib", "rlib"] + +[dependencies] +byteorder = "1.0" diff --git a/src/tools/miri/test-cargo-miri/issue-1567/src/lib.rs b/src/tools/miri/test-cargo-miri/issue-1567/src/lib.rs new file mode 100644 index 0000000000000..5479216832388 --- /dev/null +++ b/src/tools/miri/test-cargo-miri/issue-1567/src/lib.rs @@ -0,0 +1,5 @@ +use byteorder::{BigEndian, ByteOrder}; + +pub fn use_the_dependency() { + let _n = ::read_u32(&[1, 2, 3, 4]); +} diff --git a/src/tools/miri/test-cargo-miri/issue-1691/Cargo.toml b/src/tools/miri/test-cargo-miri/issue-1691/Cargo.toml new file mode 100644 index 0000000000000..3100cc6a60b58 --- /dev/null +++ b/src/tools/miri/test-cargo-miri/issue-1691/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "issue_1691" +version = "0.1.0" +authors = ["Miri Team"] +edition = "2018" + +[lib] +crate-type = ["rlib"] diff --git a/src/tools/miri/test-cargo-miri/issue-1691/src/lib.rs b/src/tools/miri/test-cargo-miri/issue-1691/src/lib.rs new file mode 100644 index 0000000000000..efde2b58e199e --- /dev/null +++ b/src/tools/miri/test-cargo-miri/issue-1691/src/lib.rs @@ -0,0 +1,3 @@ +pub fn use_me() -> bool { + true +} diff --git a/src/tools/miri/test-cargo-miri/issue-1705/Cargo.toml b/src/tools/miri/test-cargo-miri/issue-1705/Cargo.toml new file mode 100644 index 0000000000000..ae63647a88819 --- /dev/null +++ b/src/tools/miri/test-cargo-miri/issue-1705/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "issue_1705" +version = "0.1.0" +authors = ["Miri Team"] +edition = "2018" + +[lib] +crate-type = ["lib", "staticlib", "cdylib"] + +[dependencies] +byteorder = "1.0" diff --git a/src/tools/miri/test-cargo-miri/issue-1705/src/lib.rs b/src/tools/miri/test-cargo-miri/issue-1705/src/lib.rs new file mode 100644 index 0000000000000..64633490f84b9 --- /dev/null +++ b/src/tools/miri/test-cargo-miri/issue-1705/src/lib.rs @@ -0,0 +1,5 @@ +use byteorder::{ByteOrder, LittleEndian}; + +pub fn use_the_dependency() { + let _n = ::read_u32(&[1, 2, 3, 4]); +} diff --git a/src/tools/miri/test-cargo-miri/issue-1760/Cargo.toml b/src/tools/miri/test-cargo-miri/issue-1760/Cargo.toml new file mode 100644 index 0000000000000..80925c7474638 --- /dev/null +++ b/src/tools/miri/test-cargo-miri/issue-1760/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "issue_1760" +version = "0.1.0" +authors = ["Miri Team"] +edition = "2018" + +[lib] +proc-macro = true diff --git a/src/tools/miri/test-cargo-miri/issue-1760/build.rs b/src/tools/miri/test-cargo-miri/issue-1760/build.rs new file mode 100644 index 0000000000000..08427fd7164f1 --- /dev/null +++ b/src/tools/miri/test-cargo-miri/issue-1760/build.rs @@ -0,0 +1,10 @@ +use std::env; + +#[cfg(miri)] +compile_error!("`miri` cfg should not be set in build script"); + +fn main() { + // Cargo calls `miri --print=cfg` to populate the `CARGO_CFG_*` env vars. + // Make sure that the "miri" flag is not set since we are building a procedural macro crate. + assert!(env::var_os("CARGO_CFG_MIRI").is_none()); +} diff --git a/src/tools/miri/test-cargo-miri/issue-1760/src/lib.rs b/src/tools/miri/test-cargo-miri/issue-1760/src/lib.rs new file mode 100644 index 0000000000000..b4f6274af4448 --- /dev/null +++ b/src/tools/miri/test-cargo-miri/issue-1760/src/lib.rs @@ -0,0 +1,9 @@ +use proc_macro::TokenStream; + +#[cfg(miri)] +compile_error!("`miri` cfg should not be set in proc-macro"); + +#[proc_macro] +pub fn use_the_dependency(_: TokenStream) -> TokenStream { + TokenStream::new() +} diff --git a/src/tools/miri/test-cargo-miri/issue-rust-86261/Cargo.toml b/src/tools/miri/test-cargo-miri/issue-rust-86261/Cargo.toml new file mode 100644 index 0000000000000..a6b65ebb5318d --- /dev/null +++ b/src/tools/miri/test-cargo-miri/issue-rust-86261/Cargo.toml @@ -0,0 +1,5 @@ +[package] +name = "issue_rust_86261" +version = "0.1.0" +authors = ["Miri Team"] +edition = "2018" diff --git a/src/tools/miri/test-cargo-miri/issue-rust-86261/src/lib.rs b/src/tools/miri/test-cargo-miri/issue-rust-86261/src/lib.rs new file mode 100644 index 0000000000000..1947c38b77455 --- /dev/null +++ b/src/tools/miri/test-cargo-miri/issue-rust-86261/src/lib.rs @@ -0,0 +1,23 @@ +#![allow(unused_imports, unused_attributes, no_mangle_generic_items)] + +// Regression test for https://github.com/rust-lang/rust/issues/86261: +// `#[no_mangle]` on a `use` item. +#[no_mangle] +use std::{any, boxed, io, panic, string, thread}; + +// `#[no_mangle]` on a struct has a similar problem. +#[no_mangle] +pub struct NoMangleStruct; + +// If `#[no_mangle]` has effect on the `struct` above, calling `NoMangleStruct` will fail with +// "multiple definitions of symbol `NoMangleStruct`" error. +#[export_name = "NoMangleStruct"] +fn no_mangle_struct() {} + +// `#[no_mangle]` on a generic function can also cause ICEs. +#[no_mangle] +fn no_mangle_generic() {} + +// Same as `no_mangle_struct()` but for the `no_mangle_generic()` generic function. +#[export_name = "no_mangle_generic"] +fn no_mangle_generic2() {} diff --git a/src/tools/miri/test-cargo-miri/run-test.py b/src/tools/miri/test-cargo-miri/run-test.py new file mode 100755 index 0000000000000..4485d3252ccc2 --- /dev/null +++ b/src/tools/miri/test-cargo-miri/run-test.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python3 +''' +Test whether cargo-miri works properly. +Assumes the `MIRI_SYSROOT` env var to be set appropriately, +and the working directory to contain the cargo-miri-test project. +''' + +import sys, subprocess, os, re, difflib + +CGREEN = '\33[32m' +CBOLD = '\33[1m' +CEND = '\33[0m' + +def fail(msg): + print("\nTEST FAIL: {}".format(msg)) + sys.exit(1) + +def cargo_miri(cmd, quiet = True): + args = ["cargo", "miri", cmd] + if quiet: + args += ["-q"] + if 'MIRI_TEST_TARGET' in os.environ: + args += ["--target", os.environ['MIRI_TEST_TARGET']] + return args + +def normalize_stdout(str): + str = str.replace("src\\", "src/") # normalize paths across platforms + str = re.sub("finished in \d+\.\d\ds", "finished in $TIME", str) # the time keeps changing, obviously + return str + +def normalize_stderr(str): + str = re.sub("Preparing a sysroot for Miri \(target: [a-z0-9_-]+\)\.\.\. done\n", "", str) # remove leading cargo-miri setup output + return str + +def check_output(actual, path, name): + expected = open(path).read() + if expected == actual: + return True + print(f"{path} did not match reference!") + print(f"--- BEGIN diff {name} ---") + for text in difflib.unified_diff(expected.split("\n"), actual.split("\n")): + print(text) + print(f"--- END diff {name} ---") + return False + +def test(name, cmd, stdout_ref, stderr_ref, stdin=b'', env={}): + print("Testing {}...".format(name)) + ## Call `cargo miri`, capture all output + p_env = os.environ.copy() + p_env.update(env) + p = subprocess.Popen( + cmd, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=p_env, + ) + (stdout, stderr) = p.communicate(input=stdin) + stdout = normalize_stdout(stdout.decode("UTF-8")) + stderr = normalize_stderr(stderr.decode("UTF-8")) + + stdout_matches = check_output(stdout, stdout_ref, "stdout") + stderr_matches = check_output(stderr, stderr_ref, "stderr") + + if p.returncode == 0 and stdout_matches and stderr_matches: + # All good! + return + fail("exit code was {}".format(p.returncode)) + +def test_no_rebuild(name, cmd, env={}): + print("Testing {}...".format(name)) + p_env = os.environ.copy() + p_env.update(env) + p = subprocess.Popen( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=p_env, + ) + (stdout, stderr) = p.communicate() + stdout = stdout.decode("UTF-8") + stderr = stderr.decode("UTF-8") + if p.returncode != 0: + fail("rebuild failed"); + # Also check for 'Running' as a sanity check. + if stderr.count(" Compiling ") > 0 or stderr.count(" Running ") == 0: + print("--- BEGIN stderr ---") + print(stderr, end="") + print("--- END stderr ---") + fail("Something was being rebuilt when it should not be (or we got no output)"); + +def test_cargo_miri_run(): + test("`cargo miri run` (no isolation)", + cargo_miri("run"), + "run.default.stdout.ref", "run.default.stderr.ref", + stdin=b'12\n21\n', + env={ + 'MIRIFLAGS': "-Zmiri-disable-isolation", + 'MIRITESTVAR': "wrongval", # make sure the build.rs value takes precedence + }, + ) + # Special test: run it again *without* `-q` to make sure nothing is being rebuilt (Miri issue #1722) + test_no_rebuild("`cargo miri run` (no rebuild)", + cargo_miri("run", quiet=False) + ["--", ""], + env={'MIRITESTVAR': "wrongval"}, # changing the env var causes a rebuild (re-runs build.rs), + # so keep it set + ) + test("`cargo miri run` (with arguments and target)", + cargo_miri("run") + ["--bin", "cargo-miri-test", "--", "hello world", '"hello world"', r'he\\llo\"world'], + "run.args.stdout.ref", "run.args.stderr.ref", + ) + test("`cargo miri r` (subcrate, no isolation)", + cargo_miri("r") + ["-p", "subcrate"], + "run.subcrate.stdout.ref", "run.subcrate.stderr.ref", + env={'MIRIFLAGS': "-Zmiri-disable-isolation"}, + ) + test("`cargo miri run` (custom target dir)", + # Attempt to confuse the argument parser. + cargo_miri("run") + ["--target-dir=custom-run", "--", "--target-dir=target/custom-run"], + "run.args.stdout.ref", "run.custom-target-dir.stderr.ref", + ) + +def test_cargo_miri_test(): + # rustdoc is not run on foreign targets + is_foreign = 'MIRI_TEST_TARGET' in os.environ + default_ref = "test.cross-target.stdout.ref" if is_foreign else "test.default.stdout.ref" + filter_ref = "test.filter.cross-target.stdout.ref" if is_foreign else "test.filter.stdout.ref" + + # macOS needs permissive provenance inside getrandom_1. + test("`cargo miri test`", + cargo_miri("test"), + default_ref, "test.stderr-empty.ref", + env={'MIRIFLAGS': "-Zmiri-permissive-provenance -Zmiri-seed=feed"}, + ) + test("`cargo miri test` (no isolation, no doctests)", + cargo_miri("test") + ["--bins", "--tests"], # no `--lib`, we disabled that in `Cargo.toml` + "test.cross-target.stdout.ref", "test.stderr-empty.ref", + env={'MIRIFLAGS': "-Zmiri-permissive-provenance -Zmiri-disable-isolation"}, + ) + test("`cargo miri test` (with filter)", + cargo_miri("test") + ["--", "--format=pretty", "pl"], + filter_ref, "test.stderr-empty.ref", + ) + test("`cargo miri test` (test target)", + cargo_miri("test") + ["--test", "test", "--", "--format=pretty"], + "test.test-target.stdout.ref", "test.stderr-empty.ref", + env={'MIRIFLAGS': "-Zmiri-permissive-provenance"}, + ) + test("`cargo miri test` (bin target)", + cargo_miri("test") + ["--bin", "cargo-miri-test", "--", "--format=pretty"], + "test.bin-target.stdout.ref", "test.stderr-empty.ref", + ) + test("`cargo miri t` (subcrate, no isolation)", + cargo_miri("t") + ["-p", "subcrate"], + "test.subcrate.stdout.ref", "test.stderr-proc-macro.ref", + env={'MIRIFLAGS': "-Zmiri-disable-isolation"}, + ) + test("`cargo miri test` (subcrate, doctests)", + cargo_miri("test") + ["-p", "subcrate", "--doc"], + "test.stdout-empty.ref", "test.stderr-proc-macro-doctest.ref", + ) + test("`cargo miri test` (custom target dir)", + cargo_miri("test") + ["--target-dir=custom-test"], + default_ref, "test.stderr-empty.ref", + env={'MIRIFLAGS': "-Zmiri-permissive-provenance"}, + ) + del os.environ["CARGO_TARGET_DIR"] # this overrides `build.target-dir` passed by `--config`, so unset it + test("`cargo miri test` (config-cli)", + cargo_miri("test") + ["--config=build.target-dir=\"config-cli\"", "-Zunstable-options"], + default_ref, "test.stderr-empty.ref", + env={'MIRIFLAGS': "-Zmiri-permissive-provenance"}, + ) + +os.chdir(os.path.dirname(os.path.realpath(__file__))) +os.environ["CARGO_TARGET_DIR"] = "target" # this affects the location of the target directory that we need to check +os.environ["RUST_TEST_NOCAPTURE"] = "0" # this affects test output, so make sure it is not set +os.environ["RUST_TEST_THREADS"] = "1" # avoid non-deterministic output due to concurrent test runs + +target_str = " for target {}".format(os.environ['MIRI_TEST_TARGET']) if 'MIRI_TEST_TARGET' in os.environ else "" +print(CGREEN + CBOLD + "## Running `cargo miri` tests{}".format(target_str) + CEND) + +test_cargo_miri_run() +test_cargo_miri_test() +# Ensure we did not create anything outside the expected target dir. +for target_dir in ["target", "custom-run", "custom-test", "config-cli"]: + if os.listdir(target_dir) != ["miri"]: + fail(f"`{target_dir}` contains unexpected files") + # Ensure something exists inside that target dir. + os.access(os.path.join(target_dir, "miri", "debug", "deps"), os.F_OK) + +print("\nTEST SUCCESSFUL!") +sys.exit(0) diff --git a/src/tools/miri/test-cargo-miri/run.args.stderr.ref b/src/tools/miri/test-cargo-miri/run.args.stderr.ref new file mode 100644 index 0000000000000..01bb8952322b6 --- /dev/null +++ b/src/tools/miri/test-cargo-miri/run.args.stderr.ref @@ -0,0 +1,4 @@ +main +hello world +"hello world" +he\\llo\"world diff --git a/src/tools/miri/test-cargo-miri/run.args.stdout.ref b/src/tools/miri/test-cargo-miri/run.args.stdout.ref new file mode 100644 index 0000000000000..6710f307cb26d --- /dev/null +++ b/src/tools/miri/test-cargo-miri/run.args.stdout.ref @@ -0,0 +1 @@ +0x01020304 diff --git a/src/tools/miri/test-cargo-miri/run.custom-target-dir.stderr.ref b/src/tools/miri/test-cargo-miri/run.custom-target-dir.stderr.ref new file mode 100644 index 0000000000000..4395ff8879b96 --- /dev/null +++ b/src/tools/miri/test-cargo-miri/run.custom-target-dir.stderr.ref @@ -0,0 +1,2 @@ +main +--target-dir=target/custom-run diff --git a/src/tools/miri/test-cargo-miri/run.default.stderr.ref b/src/tools/miri/test-cargo-miri/run.default.stderr.ref new file mode 100644 index 0000000000000..ba2906d0666cf --- /dev/null +++ b/src/tools/miri/test-cargo-miri/run.default.stderr.ref @@ -0,0 +1 @@ +main diff --git a/src/tools/miri/test-cargo-miri/run.default.stdout.ref b/src/tools/miri/test-cargo-miri/run.default.stdout.ref new file mode 100644 index 0000000000000..2eab8df967d5f --- /dev/null +++ b/src/tools/miri/test-cargo-miri/run.default.stdout.ref @@ -0,0 +1,3 @@ +0x01020304 +24 +42 diff --git a/src/tools/miri/test-cargo-miri/run.subcrate.stderr.ref b/src/tools/miri/test-cargo-miri/run.subcrate.stderr.ref new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/tools/miri/test-cargo-miri/run.subcrate.stdout.ref b/src/tools/miri/test-cargo-miri/run.subcrate.stdout.ref new file mode 100644 index 0000000000000..53340a502381d --- /dev/null +++ b/src/tools/miri/test-cargo-miri/run.subcrate.stdout.ref @@ -0,0 +1 @@ +subcrate running diff --git a/src/tools/miri/test-cargo-miri/src/lib.rs b/src/tools/miri/test-cargo-miri/src/lib.rs new file mode 100644 index 0000000000000..66c8aa2eac57e --- /dev/null +++ b/src/tools/miri/test-cargo-miri/src/lib.rs @@ -0,0 +1,27 @@ +/// Doc-test test +/// ```rust +/// assert!(cargo_miri_test::make_true()); +/// ``` +/// ```rust,no_run +/// assert!(!cargo_miri_test::make_true()); +/// ``` +/// ```rust,compile_fail +/// assert!(cargo_miri_test::make_true() == 5); +/// ``` +#[no_mangle] +pub fn make_true() -> bool { + issue_1567::use_the_dependency(); + issue_1705::use_the_dependency(); + issue_1760::use_the_dependency!(); + issue_1691::use_me() +} + +/// ```rust +/// cargo_miri_test::miri_only_fn(); +/// ``` +#[cfg(miri)] +pub fn miri_only_fn() {} + +pub fn main() { + println!("imported main"); +} diff --git a/src/tools/miri/test-cargo-miri/src/main.rs b/src/tools/miri/test-cargo-miri/src/main.rs new file mode 100644 index 0000000000000..41c52b7017028 --- /dev/null +++ b/src/tools/miri/test-cargo-miri/src/main.rs @@ -0,0 +1,78 @@ +use byteorder::{BigEndian, ByteOrder}; +use std::env; +#[cfg(unix)] +use std::io::{self, BufRead}; + +fn main() { + // Check env var set by `build.rs`. + assert_eq!(env!("MIRITESTVAR"), "testval"); + + // Exercise external crate, printing to stdout. + let buf = &[1, 2, 3, 4]; + let n = ::read_u32(buf); + assert_eq!(n, 0x01020304); + println!("{:#010x}", n); + + // Access program arguments, printing to stderr. + for arg in std::env::args() { + eprintln!("{}", arg); + } + + // If there were no arguments, access stdin and test working dir. + // (We rely on the test runner to always disable isolation when passing no arguments.) + if std::env::args().len() <= 1 { + // CWD should be crate root. + // We have to normalize slashes, as the env var might be set for a different target's conventions. + let env_dir = env::current_dir().unwrap(); + let env_dir = env_dir.to_string_lossy().replace("\\", "/"); + let crate_dir = env::var_os("CARGO_MANIFEST_DIR").unwrap(); + let crate_dir = crate_dir.to_string_lossy().replace("\\", "/"); + assert_eq!(env_dir, crate_dir); + + #[cfg(unix)] + for line in io::stdin().lock().lines() { + let num: i32 = line.unwrap().parse().unwrap(); + println!("{}", 2 * num); + } + // On non-Unix, reading from stdin is not supported. So we hard-code the right answer. + #[cfg(not(unix))] + { + println!("24"); + println!("42"); + } + } +} + +#[cfg(test)] +mod test { + use byteorder_2::{BigEndian, ByteOrder}; + + // Make sure in-crate tests with dev-dependencies work + #[test] + fn dev_dependency() { + let _n = ::read_u64(&[1, 2, 3, 4, 5, 6, 7, 8]); + } + + #[test] + fn exported_symbol() { + extern crate cargo_miri_test; + extern crate exported_symbol; + extern crate issue_rust_86261; + // Test calling exported symbols in (transitive) dependencies. + // Repeat calls to make sure the `Instance` cache is not broken. + for _ in 0..3 { + extern "Rust" { + fn exported_symbol() -> i32; + fn assoc_fn_as_exported_symbol() -> i32; + fn make_true() -> bool; + fn NoMangleStruct(); + fn no_mangle_generic(); + } + assert_eq!(unsafe { exported_symbol() }, 123456); + assert_eq!(unsafe { assoc_fn_as_exported_symbol() }, -123456); + assert!(unsafe { make_true() }); + unsafe { NoMangleStruct() } + unsafe { no_mangle_generic() } + } + } +} diff --git a/src/tools/miri/test-cargo-miri/subcrate/Cargo.toml b/src/tools/miri/test-cargo-miri/subcrate/Cargo.toml new file mode 100644 index 0000000000000..06b1ce1cba4b8 --- /dev/null +++ b/src/tools/miri/test-cargo-miri/subcrate/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "subcrate" +version = "0.1.0" +authors = ["Miri Team"] +edition = "2018" + +[lib] +proc-macro = true +doctest = false + +[[bin]] +name = "subcrate" +path = "main.rs" + +[[test]] +name = "subtest" +path = "test.rs" +harness = false + +[dev-dependencies] +byteorder = "1.0" diff --git a/src/tools/miri/test-cargo-miri/subcrate/main.rs b/src/tools/miri/test-cargo-miri/subcrate/main.rs new file mode 100644 index 0000000000000..4ce80b3707226 --- /dev/null +++ b/src/tools/miri/test-cargo-miri/subcrate/main.rs @@ -0,0 +1,16 @@ +use std::env; +use std::path::PathBuf; + +fn main() { + println!("subcrate running"); + + // CWD should be workspace root, i.e., one level up from crate root. + // We have to normalize slashes, as the env var might be set for a different target's conventions. + let env_dir = env::current_dir().unwrap(); + let env_dir = env_dir.to_string_lossy().replace("\\", "/"); + let crate_dir = env::var_os("CARGO_MANIFEST_DIR").unwrap(); + let crate_dir = crate_dir.to_string_lossy().replace("\\", "/"); + let crate_dir = PathBuf::from(crate_dir); + let crate_dir = crate_dir.parent().unwrap().to_string_lossy(); + assert_eq!(env_dir, crate_dir); +} diff --git a/src/tools/miri/test-cargo-miri/subcrate/src/lib.rs b/src/tools/miri/test-cargo-miri/subcrate/src/lib.rs new file mode 100644 index 0000000000000..2ccb6704b05e6 --- /dev/null +++ b/src/tools/miri/test-cargo-miri/subcrate/src/lib.rs @@ -0,0 +1,5 @@ +#[cfg(doctest)] +compile_error!("rustdoc should not touch me"); + +#[cfg(test)] +compile_error!("Miri should not touch me"); diff --git a/src/tools/miri/test-cargo-miri/subcrate/test.rs b/src/tools/miri/test-cargo-miri/subcrate/test.rs new file mode 100644 index 0000000000000..77e3c2878ca0e --- /dev/null +++ b/src/tools/miri/test-cargo-miri/subcrate/test.rs @@ -0,0 +1,18 @@ +use std::env; + +use byteorder::{ByteOrder, LittleEndian}; + +fn main() { + println!("subcrate testing"); + + // CWD should be crate root. + // We have to normalize slashes, as the env var might be set for a different target's conventions. + let env_dir = env::current_dir().unwrap(); + let env_dir = env_dir.to_string_lossy().replace("\\", "/"); + let crate_dir = env::var_os("CARGO_MANIFEST_DIR").unwrap(); + let crate_dir = crate_dir.to_string_lossy().replace("\\", "/"); + assert_eq!(env_dir, crate_dir); + + // Make sure we can call dev-dependencies. + let _n = ::read_u32(&[1, 2, 3, 4]); +} diff --git a/src/tools/miri/test-cargo-miri/test.bin-target.stdout.ref b/src/tools/miri/test-cargo-miri/test.bin-target.stdout.ref new file mode 100644 index 0000000000000..5264530160bc5 --- /dev/null +++ b/src/tools/miri/test-cargo-miri/test.bin-target.stdout.ref @@ -0,0 +1,7 @@ + +running 2 tests +test test::dev_dependency ... ok +test test::exported_symbol ... ok + +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out + diff --git a/src/tools/miri/test-cargo-miri/test.cross-target.stdout.ref b/src/tools/miri/test-cargo-miri/test.cross-target.stdout.ref new file mode 100644 index 0000000000000..8c543e479f4e0 --- /dev/null +++ b/src/tools/miri/test-cargo-miri/test.cross-target.stdout.ref @@ -0,0 +1,11 @@ + +running 2 tests +.. +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out + +imported main + +running 6 tests +...i.. +test result: ok. 5 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out + diff --git a/src/tools/miri/test-cargo-miri/test.default.stdout.ref b/src/tools/miri/test-cargo-miri/test.default.stdout.ref new file mode 100644 index 0000000000000..9a17f3d61b6ac --- /dev/null +++ b/src/tools/miri/test-cargo-miri/test.default.stdout.ref @@ -0,0 +1,16 @@ + +running 2 tests +.. +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out + +imported main + +running 6 tests +...i.. +test result: ok. 5 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out + + +running 4 tests +.... +test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + diff --git a/src/tools/miri/test-cargo-miri/test.filter.cross-target.stdout.ref b/src/tools/miri/test-cargo-miri/test.filter.cross-target.stdout.ref new file mode 100644 index 0000000000000..bb0282d6c9167 --- /dev/null +++ b/src/tools/miri/test-cargo-miri/test.filter.cross-target.stdout.ref @@ -0,0 +1,12 @@ + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out + +imported main + +running 1 test +test simple ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 5 filtered out + diff --git a/src/tools/miri/test-cargo-miri/test.filter.stdout.ref b/src/tools/miri/test-cargo-miri/test.filter.stdout.ref new file mode 100644 index 0000000000000..c618956656a8a --- /dev/null +++ b/src/tools/miri/test-cargo-miri/test.filter.stdout.ref @@ -0,0 +1,17 @@ + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out + +imported main + +running 1 test +test simple ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 5 filtered out + + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 4 filtered out; finished in $TIME + diff --git a/src/tools/miri/test-cargo-miri/test.stderr-empty.ref b/src/tools/miri/test-cargo-miri/test.stderr-empty.ref new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/tools/miri/test-cargo-miri/test.stderr-proc-macro-doctest.ref b/src/tools/miri/test-cargo-miri/test.stderr-proc-macro-doctest.ref new file mode 100644 index 0000000000000..ca5e3a2392db8 --- /dev/null +++ b/src/tools/miri/test-cargo-miri/test.stderr-proc-macro-doctest.ref @@ -0,0 +1 @@ +Running doctests of `proc-macro` crates is not currently supported by Miri. diff --git a/src/tools/miri/test-cargo-miri/test.stderr-proc-macro.ref b/src/tools/miri/test-cargo-miri/test.stderr-proc-macro.ref new file mode 100644 index 0000000000000..4983250917b59 --- /dev/null +++ b/src/tools/miri/test-cargo-miri/test.stderr-proc-macro.ref @@ -0,0 +1 @@ +Running unit tests of `proc-macro` crates is not currently supported by Miri. diff --git a/src/tools/miri/test-cargo-miri/test.stdout-empty.ref b/src/tools/miri/test-cargo-miri/test.stdout-empty.ref new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/tools/miri/test-cargo-miri/test.subcrate.stdout.ref b/src/tools/miri/test-cargo-miri/test.subcrate.stdout.ref new file mode 100644 index 0000000000000..67e5c7f8e920c --- /dev/null +++ b/src/tools/miri/test-cargo-miri/test.subcrate.stdout.ref @@ -0,0 +1,6 @@ + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out + +subcrate testing diff --git a/src/tools/miri/test-cargo-miri/test.test-target.stdout.ref b/src/tools/miri/test-cargo-miri/test.test-target.stdout.ref new file mode 100644 index 0000000000000..dd59b32b780c8 --- /dev/null +++ b/src/tools/miri/test-cargo-miri/test.test-target.stdout.ref @@ -0,0 +1,11 @@ + +running 6 tests +test cargo_env ... ok +test deps ... ok +test do_panic - should panic ... ok +test does_not_work_on_miri ... ignored +test fail_index_check - should panic ... ok +test simple ... ok + +test result: ok. 5 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out + diff --git a/src/tools/miri/test-cargo-miri/tests/main.rs b/src/tools/miri/test-cargo-miri/tests/main.rs new file mode 100644 index 0000000000000..bb94c8f37876c --- /dev/null +++ b/src/tools/miri/test-cargo-miri/tests/main.rs @@ -0,0 +1,3 @@ +#![feature(imported_main)] + +use cargo_miri_test::main; diff --git a/src/tools/miri/test-cargo-miri/tests/test.rs b/src/tools/miri/test-cargo-miri/tests/test.rs new file mode 100644 index 0000000000000..9ed2152893964 --- /dev/null +++ b/src/tools/miri/test-cargo-miri/tests/test.rs @@ -0,0 +1,49 @@ +#[test] +fn simple() { + assert_eq!(4, 4); +} + +// A test that won't work on miri (tests disabling tests). +#[test] +#[cfg_attr(miri, ignore)] +fn does_not_work_on_miri() { + // Only do this where inline assembly is stable. + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + unsafe { + std::arch::asm!("foo"); + } +} + +// Make sure integration tests can access both dependencies and dev-dependencies +#[test] +fn deps() { + { + use byteorder::{BigEndian, ByteOrder}; + let _n = ::read_u64(&[1, 2, 3, 4, 5, 6, 7, 8]); + } + { + use byteorder_2::{BigEndian, ByteOrder}; + let _n = ::read_u64(&[1, 2, 3, 4, 5, 6, 7, 8]); + } +} + +#[test] +fn cargo_env() { + assert_eq!(env!("CARGO_PKG_NAME"), "cargo-miri-test"); + env!("CARGO_BIN_EXE_cargo-miri-test"); // Asserts that this exists. +} + +#[test] +#[should_panic(expected = "Explicit panic")] +fn do_panic() // In large, friendly letters :) +{ + panic!("Explicit panic from test!"); +} + +// A different way of raising a panic +#[test] +#[allow(unconditional_panic)] +#[should_panic(expected = "the len is 0 but the index is 42")] +fn fail_index_check() { + [][42] +} diff --git a/src/tools/miri/test_dependencies/Cargo.lock b/src/tools/miri/test_dependencies/Cargo.lock new file mode 100644 index 0000000000000..d4b32e2c29a22 --- /dev/null +++ b/src/tools/miri/test_dependencies/Cargo.lock @@ -0,0 +1,388 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "libc" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" + +[[package]] +name = "lock_api" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "mio" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys", +] + +[[package]] +name = "miri-test-deps" +version = "0.1.0" +dependencies = [ + "getrandom 0.1.16", + "getrandom 0.2.7", + "libc", + "num_cpus", + "page_size", + "rand", + "tokio", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" + +[[package]] +name = "page_size" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eebde548fbbf1ea81a99b128872779c437752fb99f217c45245e1a61dcd9edcd" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "proc-macro2" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom 0.2.7", +] + +[[package]] +name = "redox_syscall" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +dependencies = [ + "bitflags", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "smallvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" + +[[package]] +name = "socket2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "syn" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tokio" +version = "1.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439" +dependencies = [ + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "once_cell", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "winapi", +] + +[[package]] +name = "tokio-macros" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" diff --git a/src/tools/miri/test_dependencies/Cargo.toml b/src/tools/miri/test_dependencies/Cargo.toml new file mode 100644 index 0000000000000..58f731f91d0f4 --- /dev/null +++ b/src/tools/miri/test_dependencies/Cargo.toml @@ -0,0 +1,21 @@ +[package] +authors = ["Miri Team"] +description = "dependencies that unit tests can have" +license = "MIT OR Apache-2.0" +name = "miri-test-deps" +repository = "https://github.com/rust-lang/miri" +version = "0.1.0" +edition = "2021" + +[dependencies] +# all dependencies (and their transitive ones) listed here can be used in `tests/`. +tokio = { version = "1.0", features = ["full"] } +libc = "0.2" +page_size = "0.4.1" +num_cpus = "1.10.1" + +getrandom_1 = { package = "getrandom", version = "0.1" } +getrandom_2 = { package = "getrandom", version = "0.2" } +rand = { version = "0.8", features = ["small_rng"] } + +[workspace] diff --git a/src/tools/miri/test_dependencies/src/main.rs b/src/tools/miri/test_dependencies/src/main.rs new file mode 100644 index 0000000000000..f328e4d9d04c3 --- /dev/null +++ b/src/tools/miri/test_dependencies/src/main.rs @@ -0,0 +1 @@ +fn main() {} diff --git a/src/tools/miri/tests/compiletest.rs b/src/tools/miri/tests/compiletest.rs new file mode 100644 index 0000000000000..6b5668e2d6c4c --- /dev/null +++ b/src/tools/miri/tests/compiletest.rs @@ -0,0 +1,220 @@ +use colored::*; +use regex::Regex; +use std::path::{Path, PathBuf}; +use std::{env, process::Command}; +use ui_test::{color_eyre::Result, Config, Mode, OutputConflictHandling}; + +fn miri_path() -> PathBuf { + PathBuf::from(option_env!("MIRI").unwrap_or(env!("CARGO_BIN_EXE_miri"))) +} + +// Build the shared object file for testing external C function calls. +fn build_so_for_c_ffi_tests() -> PathBuf { + let cc = option_env!("CC").unwrap_or("cc"); + // Target directory that we can write to. + let so_target_dir = Path::new(&env::var_os("CARGO_TARGET_DIR").unwrap()).join("miri-extern-so"); + // Create the directory if it does not already exist. + std::fs::create_dir_all(&so_target_dir) + .expect("Failed to create directory for shared object file"); + let so_file_path = so_target_dir.join("libtestlib.so"); + let cc_output = Command::new(cc) + .args([ + "-shared", + "-o", + so_file_path.to_str().unwrap(), + "tests/extern-so/test.c", + // Only add the functions specified in libcode.version to the shared object file. + // This is to avoid automatically adding `malloc`, etc. + // Source: https://anadoxin.org/blog/control-over-symbol-exports-in-gcc.html/ + "-fPIC", + "-Wl,--version-script=tests/extern-so/libcode.version", + ]) + .output() + .expect("failed to generate shared object file for testing external C function calls"); + if !cc_output.status.success() { + panic!("error in generating shared object file for testing external C function calls"); + } + so_file_path +} + +fn run_tests( + mode: Mode, + path: &str, + target: Option, + with_dependencies: bool, +) -> Result<()> { + let mut config = Config { + target, + stderr_filters: STDERR.clone(), + stdout_filters: STDOUT.clone(), + root_dir: PathBuf::from(path), + mode, + program: miri_path(), + quiet: false, + ..Config::default() + }; + + let in_rustc_test_suite = option_env!("RUSTC_STAGE").is_some(); + + // Add some flags we always want. + config.args.push("--edition".into()); + config.args.push("2018".into()); + if in_rustc_test_suite { + // Less aggressive warnings to make the rustc toolstate management less painful. + // (We often get warnings when e.g. a feature gets stabilized or some lint gets added/improved.) + config.args.push("-Astable-features".into()); + config.args.push("-Aunused".into()); + } else { + config.args.push("-Dwarnings".into()); + config.args.push("-Dunused".into()); + } + if let Ok(extra_flags) = env::var("MIRIFLAGS") { + for flag in extra_flags.split_whitespace() { + config.args.push(flag.into()); + } + } + config.args.push("-Zui-testing".into()); + if let Some(target) = &config.target { + config.args.push("--target".into()); + config.args.push(target.into()); + } + + // If we're on linux, and we're testing the extern-so functionality, + // then build the shared object file for testing external C function calls + // and push the relevant compiler flag. + if cfg!(target_os = "linux") && path.starts_with("tests/extern-so/") { + let so_file_path = build_so_for_c_ffi_tests(); + let mut flag = std::ffi::OsString::from("-Zmiri-extern-so-file="); + flag.push(so_file_path.into_os_string()); + config.args.push(flag); + } + + let skip_ui_checks = env::var_os("MIRI_SKIP_UI_CHECKS").is_some(); + + config.output_conflict_handling = match (env::var_os("MIRI_BLESS").is_some(), skip_ui_checks) { + (false, false) => OutputConflictHandling::Error, + (true, false) => OutputConflictHandling::Bless, + (false, true) => OutputConflictHandling::Ignore, + (true, true) => panic!("cannot use MIRI_BLESS and MIRI_SKIP_UI_CHECKS at the same time"), + }; + + // Handle command-line arguments. + config.path_filter.extend(std::env::args().skip(1).filter(|arg| { + match &**arg { + "--quiet" => { + config.quiet = true; + false + } + _ => true, + } + })); + + let use_std = env::var_os("MIRI_NO_STD").is_none(); + + if with_dependencies && use_std { + config.dependencies_crate_manifest_path = + Some(Path::new("test_dependencies").join("Cargo.toml")); + config.dependency_builder.args = vec![ + "run".into(), + "--manifest-path".into(), + "cargo-miri/Cargo.toml".into(), + "--".into(), + "miri".into(), + "run".into(), // There is no `cargo miri build` so we just use `cargo miri run`. + ]; + } + ui_test::run_tests(config) +} + +macro_rules! regexes { + ($name:ident: $($regex:expr => $replacement:expr,)*) => {lazy_static::lazy_static! { + static ref $name: Vec<(Regex, &'static str)> = vec![ + $((Regex::new($regex).unwrap(), $replacement),)* + ]; + }}; +} + +regexes! { + STDOUT: + // Windows file paths + r"\\" => "/", +} + +regexes! { + STDERR: + // erase line and column info + r"\.rs:[0-9]+:[0-9]+(: [0-9]+:[0-9]+)?" => ".rs:LL:CC", + // erase alloc ids + "alloc[0-9]+" => "ALLOC", + // erase Stacked Borrows tags + "<[0-9]+>" => "", + // erase whitespace that differs between platforms + r" +at (.*\.rs)" => " at $1", + // erase generics in backtraces + "([0-9]+: .*)::<.*>" => "$1", + // erase addresses in backtraces + "([0-9]+: ) +0x[0-9a-f]+ - (.*)" => "$1$2", + // erase long hexadecimals + r"0x[0-9a-fA-F]+[0-9a-fA-F]{2,2}" => "$$HEX", + // erase specific alignments + "alignment [0-9]+" => "alignment ALIGN", + // erase thread caller ids + r"call [0-9]+" => "call ID", + // erase platform module paths + "sys::[a-z]+::" => "sys::PLATFORM::", + // Windows file paths + r"\\" => "/", + // erase Rust stdlib path + "[^ `]*/(rust[^/]*|checkout)/library/" => "RUSTLIB/", + // erase platform file paths + "sys/[a-z]+/" => "sys/PLATFORM/", + // erase paths into the crate registry + r"[^ ]*/\.?cargo/registry/.*/(.*\.rs)" => "CARGO_REGISTRY/.../$1", +} + +enum Dependencies { + WithDependencies, + WithoutDependencies, +} + +use Dependencies::*; + +fn ui(mode: Mode, path: &str, with_dependencies: Dependencies) -> Result<()> { + let target = get_target(); + + let msg = format!( + "## Running ui tests in {path} against miri for {}", + target.as_deref().unwrap_or("host") + ); + eprintln!("{}", msg.green().bold()); + + let with_dependencies = match with_dependencies { + WithDependencies => true, + WithoutDependencies => false, + }; + run_tests(mode, path, target, with_dependencies) +} + +fn get_target() -> Option { + env::var("MIRI_TEST_TARGET").ok() +} + +fn main() -> Result<()> { + ui_test::color_eyre::install()?; + + // Add a test env var to do environment communication tests. + env::set_var("MIRI_ENV_VAR_TEST", "0"); + // Let the tests know where to store temp files (they might run for a different target, which can make this hard to find). + env::set_var("MIRI_TEMP", env::temp_dir()); + + ui(Mode::Pass, "tests/pass", WithoutDependencies)?; + ui(Mode::Pass, "tests/pass-dep", WithDependencies)?; + ui(Mode::Panic, "tests/panic", WithDependencies)?; + ui(Mode::Fail { require_patterns: true }, "tests/fail", WithDependencies)?; + if cfg!(target_os = "linux") { + ui(Mode::Pass, "tests/extern-so/pass", WithoutDependencies)?; + ui(Mode::Fail { require_patterns: true }, "tests/extern-so/fail", WithDependencies)?; + } + + Ok(()) +} diff --git a/src/tools/miri/tests/extern-so/fail/function_not_in_so.rs b/src/tools/miri/tests/extern-so/fail/function_not_in_so.rs new file mode 100644 index 0000000000000..3aaeb632cad7b --- /dev/null +++ b/src/tools/miri/tests/extern-so/fail/function_not_in_so.rs @@ -0,0 +1,12 @@ +//@only-target-linux +//@only-on-host + +extern "C" { + fn foo(); +} + +fn main() { + unsafe { + foo(); //~ ERROR: unsupported operation: can't call foreign function: foo + } +} diff --git a/src/tools/miri/tests/extern-so/fail/function_not_in_so.stderr b/src/tools/miri/tests/extern-so/fail/function_not_in_so.stderr new file mode 100644 index 0000000000000..f649f0ae43e30 --- /dev/null +++ b/src/tools/miri/tests/extern-so/fail/function_not_in_so.stderr @@ -0,0 +1,14 @@ +error: unsupported operation: can't call foreign function: foo + --> $DIR/function_not_in_so.rs:LL:CC + | +LL | foo(); + | ^^^^^ can't call foreign function: foo + | + = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support + = note: BACKTRACE: + = note: inside `main` at $DIR/function_not_in_so.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/extern-so/libcode.version b/src/tools/miri/tests/extern-so/libcode.version new file mode 100644 index 0000000000000..0f04b9aaebb38 --- /dev/null +++ b/src/tools/miri/tests/extern-so/libcode.version @@ -0,0 +1,9 @@ +CODEABI_1.0 { + global: *add_one_int*; + *printer*; + *test_stack_spill*; + *get_unsigned_int*; + *add_int16*; + *add_short_to_long*; + local: *; +}; diff --git a/src/tools/miri/tests/extern-so/pass/call_extern_c_fn.rs b/src/tools/miri/tests/extern-so/pass/call_extern_c_fn.rs new file mode 100644 index 0000000000000..1e1d0b11e99ff --- /dev/null +++ b/src/tools/miri/tests/extern-so/pass/call_extern_c_fn.rs @@ -0,0 +1,46 @@ +//@only-target-linux +//@only-on-host + +extern "C" { + fn add_one_int(x: i32) -> i32; + fn add_int16(x: i16) -> i16; + fn test_stack_spill( + a: i32, + b: i32, + c: i32, + d: i32, + e: i32, + f: i32, + g: i32, + h: i32, + i: i32, + j: i32, + k: i32, + l: i32, + ) -> i32; + fn add_short_to_long(x: i16, y: i64) -> i64; + fn get_unsigned_int() -> u32; + fn printer(); +} + +fn main() { + unsafe { + // test function that adds 2 to a provided int + assert_eq!(add_one_int(1), 3); + + // test function that takes the sum of its 12 arguments + assert_eq!(test_stack_spill(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12), 78); + + // test function that adds 3 to a 16 bit int + assert_eq!(add_int16(-1i16), 2i16); + + // test function that adds an i16 to an i64 + assert_eq!(add_short_to_long(-1i16, 123456789123i64), 123456789122i64); + + // test function that returns -10 as an unsigned int + assert_eq!(get_unsigned_int(), (-10i32) as u32); + + // test void function that prints from C + printer(); + } +} diff --git a/src/tools/miri/tests/extern-so/pass/call_extern_c_fn.stdout b/src/tools/miri/tests/extern-so/pass/call_extern_c_fn.stdout new file mode 100644 index 0000000000000..7ba13d2d7b02e --- /dev/null +++ b/src/tools/miri/tests/extern-so/pass/call_extern_c_fn.stdout @@ -0,0 +1 @@ +printing from C diff --git a/src/tools/miri/tests/extern-so/test.c b/src/tools/miri/tests/extern-so/test.c new file mode 100644 index 0000000000000..68714f1743b6e --- /dev/null +++ b/src/tools/miri/tests/extern-so/test.c @@ -0,0 +1,27 @@ +#include + +int add_one_int(int x) { + return 2 + x; +} + +void printer() { + printf("printing from C\n"); +} + +// function with many arguments, to test functionality when some args are stored +// on the stack +int test_stack_spill(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k, int l) { + return a+b+c+d+e+f+g+h+i+j+k+l; +} + +unsigned int get_unsigned_int() { + return -10; +} + +short add_int16(short x) { + return x + 3; +} + +long add_short_to_long(short x, long y) { + return x + y; +} diff --git a/src/tools/miri/tests/fail/abort-terminator.rs b/src/tools/miri/tests/fail/abort-terminator.rs new file mode 100644 index 0000000000000..c954443a27629 --- /dev/null +++ b/src/tools/miri/tests/fail/abort-terminator.rs @@ -0,0 +1,10 @@ +#![feature(c_unwind)] + +extern "C" fn panic_abort() { + //~^ ERROR: the program aborted + panic!() +} + +fn main() { + panic_abort(); +} diff --git a/src/tools/miri/tests/fail/abort-terminator.stderr b/src/tools/miri/tests/fail/abort-terminator.stderr new file mode 100644 index 0000000000000..ec9ce76685b55 --- /dev/null +++ b/src/tools/miri/tests/fail/abort-terminator.stderr @@ -0,0 +1,22 @@ +thread 'main' panicked at 'explicit panic', $DIR/abort-terminator.rs:LL:CC +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +error: abnormal termination: the program aborted execution + --> $DIR/abort-terminator.rs:LL:CC + | +LL | / extern "C" fn panic_abort() { +LL | | +LL | | panic!() +LL | | } + | |_^ the program aborted execution + | + = note: inside `panic_abort` at $DIR/abort-terminator.rs:LL:CC +note: inside `main` at $DIR/abort-terminator.rs:LL:CC + --> $DIR/abort-terminator.rs:LL:CC + | +LL | panic_abort(); + | ^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/alloc/deallocate-bad-alignment.rs b/src/tools/miri/tests/fail/alloc/deallocate-bad-alignment.rs new file mode 100644 index 0000000000000..a07d8254ad3d5 --- /dev/null +++ b/src/tools/miri/tests/fail/alloc/deallocate-bad-alignment.rs @@ -0,0 +1,10 @@ +use std::alloc::{alloc, dealloc, Layout}; + +//@error-pattern: has size 1 and alignment 1, but gave size 1 and alignment 2 + +fn main() { + unsafe { + let x = alloc(Layout::from_size_align_unchecked(1, 1)); + dealloc(x, Layout::from_size_align_unchecked(1, 2)); + } +} diff --git a/src/tools/miri/tests/fail/alloc/deallocate-bad-alignment.stderr b/src/tools/miri/tests/fail/alloc/deallocate-bad-alignment.stderr new file mode 100644 index 0000000000000..28439b54b2908 --- /dev/null +++ b/src/tools/miri/tests/fail/alloc/deallocate-bad-alignment.stderr @@ -0,0 +1,20 @@ +error: Undefined Behavior: incorrect layout on deallocation: ALLOC has size 1 and alignment ALIGN, but gave size 1 and alignment ALIGN + --> RUSTLIB/alloc/src/alloc.rs:LL:CC + | +LL | unsafe { __rust_dealloc(ptr, layout.size(), layout.align()) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ incorrect layout on deallocation: ALLOC has size 1 and alignment ALIGN, but gave size 1 and alignment ALIGN + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `std::alloc::dealloc` at RUSTLIB/alloc/src/alloc.rs:LL:CC +note: inside `main` at $DIR/deallocate-bad-alignment.rs:LL:CC + --> $DIR/deallocate-bad-alignment.rs:LL:CC + | +LL | dealloc(x, Layout::from_size_align_unchecked(1, 2)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/alloc/deallocate-bad-size.rs b/src/tools/miri/tests/fail/alloc/deallocate-bad-size.rs new file mode 100644 index 0000000000000..47aaef1935e90 --- /dev/null +++ b/src/tools/miri/tests/fail/alloc/deallocate-bad-size.rs @@ -0,0 +1,10 @@ +use std::alloc::{alloc, dealloc, Layout}; + +//@error-pattern: has size 1 and alignment 1, but gave size 2 and alignment 1 + +fn main() { + unsafe { + let x = alloc(Layout::from_size_align_unchecked(1, 1)); + dealloc(x, Layout::from_size_align_unchecked(2, 1)); + } +} diff --git a/src/tools/miri/tests/fail/alloc/deallocate-bad-size.stderr b/src/tools/miri/tests/fail/alloc/deallocate-bad-size.stderr new file mode 100644 index 0000000000000..a6ceab1f56f51 --- /dev/null +++ b/src/tools/miri/tests/fail/alloc/deallocate-bad-size.stderr @@ -0,0 +1,20 @@ +error: Undefined Behavior: incorrect layout on deallocation: ALLOC has size 1 and alignment ALIGN, but gave size 2 and alignment ALIGN + --> RUSTLIB/alloc/src/alloc.rs:LL:CC + | +LL | unsafe { __rust_dealloc(ptr, layout.size(), layout.align()) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ incorrect layout on deallocation: ALLOC has size 1 and alignment ALIGN, but gave size 2 and alignment ALIGN + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `std::alloc::dealloc` at RUSTLIB/alloc/src/alloc.rs:LL:CC +note: inside `main` at $DIR/deallocate-bad-size.rs:LL:CC + --> $DIR/deallocate-bad-size.rs:LL:CC + | +LL | dealloc(x, Layout::from_size_align_unchecked(2, 1)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/alloc/deallocate-twice.rs b/src/tools/miri/tests/fail/alloc/deallocate-twice.rs new file mode 100644 index 0000000000000..1eb9bbf91ca5c --- /dev/null +++ b/src/tools/miri/tests/fail/alloc/deallocate-twice.rs @@ -0,0 +1,11 @@ +use std::alloc::{alloc, dealloc, Layout}; + +//@error-pattern: dereferenced after this allocation got freed + +fn main() { + unsafe { + let x = alloc(Layout::from_size_align_unchecked(1, 1)); + dealloc(x, Layout::from_size_align_unchecked(1, 1)); + dealloc(x, Layout::from_size_align_unchecked(1, 1)); + } +} diff --git a/src/tools/miri/tests/fail/alloc/deallocate-twice.stderr b/src/tools/miri/tests/fail/alloc/deallocate-twice.stderr new file mode 100644 index 0000000000000..b6c5b6f97ee7b --- /dev/null +++ b/src/tools/miri/tests/fail/alloc/deallocate-twice.stderr @@ -0,0 +1,20 @@ +error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed + --> RUSTLIB/alloc/src/alloc.rs:LL:CC + | +LL | unsafe { __rust_dealloc(ptr, layout.size(), layout.align()) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ pointer to ALLOC was dereferenced after this allocation got freed + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `std::alloc::dealloc` at RUSTLIB/alloc/src/alloc.rs:LL:CC +note: inside `main` at $DIR/deallocate-twice.rs:LL:CC + --> $DIR/deallocate-twice.rs:LL:CC + | +LL | dealloc(x, Layout::from_size_align_unchecked(1, 1)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/alloc/global_system_mixup.rs b/src/tools/miri/tests/fail/alloc/global_system_mixup.rs new file mode 100644 index 0000000000000..47b098c71a2bf --- /dev/null +++ b/src/tools/miri/tests/fail/alloc/global_system_mixup.rs @@ -0,0 +1,19 @@ +// Make sure we detect when the `Global` and `System` allocators are mixed +// (even when the default `Global` uses `System`). +//@error-pattern: /deallocating .*, which is Rust heap memory, using .* heap deallocation operation/ + +//@normalize-stderr-test: "using [A-Za-z]+ heap deallocation operation" -> "using PLATFORM heap deallocation operation" +//@normalize-stderr-test: "\| +\^+" -> "| ^" +//@normalize-stderr-test: "libc::free\([^()]*\)|unsafe \{ HeapFree\([^()]*\) \};" -> "FREE();" + +#![feature(allocator_api, slice_ptr_get)] + +use std::alloc::{Allocator, Global, Layout, System}; + +fn main() { + let l = Layout::from_size_align(1, 1).unwrap(); + let ptr = Global.allocate(l).unwrap().as_non_null_ptr(); + unsafe { + System.deallocate(ptr, l); + } +} diff --git a/src/tools/miri/tests/fail/alloc/global_system_mixup.stderr b/src/tools/miri/tests/fail/alloc/global_system_mixup.stderr new file mode 100644 index 0000000000000..4ee85add6c228 --- /dev/null +++ b/src/tools/miri/tests/fail/alloc/global_system_mixup.stderr @@ -0,0 +1,21 @@ +error: Undefined Behavior: deallocating ALLOC, which is Rust heap memory, using PLATFORM heap deallocation operation + --> RUSTLIB/std/src/sys/PLATFORM/alloc.rs:LL:CC + | +LL | FREE(); + | ^ deallocating ALLOC, which is Rust heap memory, using PLATFORM heap deallocation operation + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `std::sys::PLATFORM::alloc::::dealloc` at RUSTLIB/std/src/sys/PLATFORM/alloc.rs:LL:CC + = note: inside `::deallocate` at RUSTLIB/std/src/alloc.rs:LL:CC +note: inside `main` at $DIR/global_system_mixup.rs:LL:CC + --> $DIR/global_system_mixup.rs:LL:CC + | +LL | System.deallocate(ptr, l); + | ^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/alloc/no_global_allocator.rs b/src/tools/miri/tests/fail/alloc/no_global_allocator.rs new file mode 100644 index 0000000000000..fb0e7986bb5e6 --- /dev/null +++ b/src/tools/miri/tests/fail/alloc/no_global_allocator.rs @@ -0,0 +1,25 @@ +// Make sure we pretend the allocation symbols don't exist when there is no allocator + +#![feature(lang_items, start)] +#![no_std] + +extern "Rust" { + fn __rust_alloc(size: usize, align: usize) -> *mut u8; +} + +#[start] +fn start(_: isize, _: *const *const u8) -> isize { + unsafe { + __rust_alloc(1, 1); //~ERROR: unsupported operation: can't call foreign function: __rust_alloc + } + + 0 +} + +#[panic_handler] +fn panic_handler(_: &core::panic::PanicInfo) -> ! { + loop {} +} + +#[lang = "eh_personality"] +fn eh_personality() {} diff --git a/src/tools/miri/tests/fail/alloc/no_global_allocator.stderr b/src/tools/miri/tests/fail/alloc/no_global_allocator.stderr new file mode 100644 index 0000000000000..ea70970ae0fef --- /dev/null +++ b/src/tools/miri/tests/fail/alloc/no_global_allocator.stderr @@ -0,0 +1,12 @@ +error: unsupported operation: can't call foreign function: __rust_alloc + --> $DIR/no_global_allocator.rs:LL:CC + | +LL | __rust_alloc(1, 1); + | ^^^^^^^^^^^^^^^^^^ can't call foreign function: __rust_alloc + | + = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support + = note: BACKTRACE: + = note: inside `start` at $DIR/no_global_allocator.rs:LL:CC + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/alloc/reallocate-bad-size.rs b/src/tools/miri/tests/fail/alloc/reallocate-bad-size.rs new file mode 100644 index 0000000000000..145c3393d677a --- /dev/null +++ b/src/tools/miri/tests/fail/alloc/reallocate-bad-size.rs @@ -0,0 +1,10 @@ +use std::alloc::{alloc, realloc, Layout}; + +//@error-pattern: has size 1 and alignment 1, but gave size 2 and alignment 1 + +fn main() { + unsafe { + let x = alloc(Layout::from_size_align_unchecked(1, 1)); + let _y = realloc(x, Layout::from_size_align_unchecked(2, 1), 1); + } +} diff --git a/src/tools/miri/tests/fail/alloc/reallocate-bad-size.stderr b/src/tools/miri/tests/fail/alloc/reallocate-bad-size.stderr new file mode 100644 index 0000000000000..c11b5a851048f --- /dev/null +++ b/src/tools/miri/tests/fail/alloc/reallocate-bad-size.stderr @@ -0,0 +1,20 @@ +error: Undefined Behavior: incorrect layout on deallocation: ALLOC has size 1 and alignment ALIGN, but gave size 2 and alignment ALIGN + --> RUSTLIB/alloc/src/alloc.rs:LL:CC + | +LL | unsafe { __rust_realloc(ptr, layout.size(), layout.align(), new_size) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ incorrect layout on deallocation: ALLOC has size 1 and alignment ALIGN, but gave size 2 and alignment ALIGN + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `std::alloc::realloc` at RUSTLIB/alloc/src/alloc.rs:LL:CC +note: inside `main` at $DIR/reallocate-bad-size.rs:LL:CC + --> $DIR/reallocate-bad-size.rs:LL:CC + | +LL | let _y = realloc(x, Layout::from_size_align_unchecked(2, 1), 1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/alloc/reallocate-change-alloc.rs b/src/tools/miri/tests/fail/alloc/reallocate-change-alloc.rs new file mode 100644 index 0000000000000..3ad56da2c2fff --- /dev/null +++ b/src/tools/miri/tests/fail/alloc/reallocate-change-alloc.rs @@ -0,0 +1,9 @@ +use std::alloc::{alloc, realloc, Layout}; + +fn main() { + unsafe { + let x = alloc(Layout::from_size_align_unchecked(1, 1)); + let _y = realloc(x, Layout::from_size_align_unchecked(1, 1), 1); + let _z = *x; //~ ERROR: dereferenced after this allocation got freed + } +} diff --git a/src/tools/miri/tests/fail/alloc/reallocate-change-alloc.stderr b/src/tools/miri/tests/fail/alloc/reallocate-change-alloc.stderr new file mode 100644 index 0000000000000..5631dcb4cc084 --- /dev/null +++ b/src/tools/miri/tests/fail/alloc/reallocate-change-alloc.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed + --> $DIR/reallocate-change-alloc.rs:LL:CC + | +LL | let _z = *x; + | ^^ pointer to ALLOC was dereferenced after this allocation got freed + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/reallocate-change-alloc.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/alloc/reallocate-dangling.rs b/src/tools/miri/tests/fail/alloc/reallocate-dangling.rs new file mode 100644 index 0000000000000..34f1658344a5e --- /dev/null +++ b/src/tools/miri/tests/fail/alloc/reallocate-dangling.rs @@ -0,0 +1,11 @@ +use std::alloc::{alloc, dealloc, realloc, Layout}; + +//@error-pattern: dereferenced after this allocation got freed + +fn main() { + unsafe { + let x = alloc(Layout::from_size_align_unchecked(1, 1)); + dealloc(x, Layout::from_size_align_unchecked(1, 1)); + let _z = realloc(x, Layout::from_size_align_unchecked(1, 1), 1); + } +} diff --git a/src/tools/miri/tests/fail/alloc/reallocate-dangling.stderr b/src/tools/miri/tests/fail/alloc/reallocate-dangling.stderr new file mode 100644 index 0000000000000..c7db5a729048c --- /dev/null +++ b/src/tools/miri/tests/fail/alloc/reallocate-dangling.stderr @@ -0,0 +1,20 @@ +error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed + --> RUSTLIB/alloc/src/alloc.rs:LL:CC + | +LL | unsafe { __rust_realloc(ptr, layout.size(), layout.align(), new_size) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ pointer to ALLOC was dereferenced after this allocation got freed + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `std::alloc::realloc` at RUSTLIB/alloc/src/alloc.rs:LL:CC +note: inside `main` at $DIR/reallocate-dangling.rs:LL:CC + --> $DIR/reallocate-dangling.rs:LL:CC + | +LL | let _z = realloc(x, Layout::from_size_align_unchecked(1, 1), 1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/alloc/stack_free.rs b/src/tools/miri/tests/fail/alloc/stack_free.rs new file mode 100644 index 0000000000000..baf53decc4413 --- /dev/null +++ b/src/tools/miri/tests/fail/alloc/stack_free.rs @@ -0,0 +1,10 @@ +// Validation/SB changes why we fail +//@compile-flags: -Zmiri-disable-validation -Zmiri-disable-stacked-borrows + +//@error-pattern: /deallocating .*, which is stack variable memory, using Rust heap deallocation operation/ + +fn main() { + let x = 42; + let bad_box = unsafe { std::mem::transmute::<&i32, Box>(&x) }; + drop(bad_box); +} diff --git a/src/tools/miri/tests/fail/alloc/stack_free.stderr b/src/tools/miri/tests/fail/alloc/stack_free.stderr new file mode 100644 index 0000000000000..44991542b1350 --- /dev/null +++ b/src/tools/miri/tests/fail/alloc/stack_free.stderr @@ -0,0 +1,24 @@ +error: Undefined Behavior: deallocating ALLOC, which is stack variable memory, using Rust heap deallocation operation + --> RUSTLIB/alloc/src/alloc.rs:LL:CC + | +LL | unsafe { __rust_dealloc(ptr, layout.size(), layout.align()) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ deallocating ALLOC, which is stack variable memory, using Rust heap deallocation operation + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `std::alloc::dealloc` at RUSTLIB/alloc/src/alloc.rs:LL:CC + = note: inside `::deallocate` at RUSTLIB/alloc/src/alloc.rs:LL:CC + = note: inside `alloc::alloc::box_free::` at RUSTLIB/alloc/src/alloc.rs:LL:CC + = note: inside `std::ptr::drop_in_place::> - shim(Some(std::boxed::Box))` at RUSTLIB/core/src/ptr/mod.rs:LL:CC + = note: inside `std::mem::drop::>` at RUSTLIB/core/src/mem/mod.rs:LL:CC +note: inside `main` at $DIR/stack_free.rs:LL:CC + --> $DIR/stack_free.rs:LL:CC + | +LL | drop(bad_box); + | ^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/box-cell-alias.rs b/src/tools/miri/tests/fail/box-cell-alias.rs new file mode 100644 index 0000000000000..1a4a3b97ea3ff --- /dev/null +++ b/src/tools/miri/tests/fail/box-cell-alias.rs @@ -0,0 +1,18 @@ +//@compile-flags: -Zmiri-strict-provenance + +// Taken from . + +use std::cell::Cell; + +fn helper(val: Box>, ptr: *const Cell) -> u8 { + val.set(10); + unsafe { (*ptr).set(20) }; //~ ERROR: does not exist in the borrow stack + val.get() +} + +fn main() { + let val: Box> = Box::new(Cell::new(25)); + let ptr: *const Cell = &*val; + let res = helper(val, ptr); + assert_eq!(res, 20); +} diff --git a/src/tools/miri/tests/fail/box-cell-alias.stderr b/src/tools/miri/tests/fail/box-cell-alias.stderr new file mode 100644 index 0000000000000..8370163997687 --- /dev/null +++ b/src/tools/miri/tests/fail/box-cell-alias.stderr @@ -0,0 +1,33 @@ +error: Undefined Behavior: trying to retag from for SharedReadWrite permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + --> $DIR/box-cell-alias.rs:LL:CC + | +LL | unsafe { (*ptr).set(20) }; + | ^^^^^^^^^^^^^^ + | | + | trying to retag from for SharedReadWrite permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + | this error occurs as part of retag at ALLOC[0x0..0x1] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a SharedReadWrite retag at offsets [0x0..0x1] + --> $DIR/box-cell-alias.rs:LL:CC + | +LL | let ptr: *const Cell = &*val; + | ^^^^^ +help: was later invalidated at offsets [0x0..0x1] by a Unique retag + --> $DIR/box-cell-alias.rs:LL:CC + | +LL | let res = helper(val, ptr); + | ^^^ + = note: BACKTRACE: + = note: inside `helper` at $DIR/box-cell-alias.rs:LL:CC +note: inside `main` at $DIR/box-cell-alias.rs:LL:CC + --> $DIR/box-cell-alias.rs:LL:CC + | +LL | let res = helper(val, ptr); + | ^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/branchless-select-i128-pointer.rs b/src/tools/miri/tests/fail/branchless-select-i128-pointer.rs new file mode 100644 index 0000000000000..2b861e5447b03 --- /dev/null +++ b/src/tools/miri/tests/fail/branchless-select-i128-pointer.rs @@ -0,0 +1,22 @@ +use std::mem::transmute; + +#[cfg(target_pointer_width = "32")] +type TwoPtrs = i64; +#[cfg(target_pointer_width = "64")] +type TwoPtrs = i128; + +fn main() { + for &my_bool in &[true, false] { + let mask = -(my_bool as TwoPtrs); // false -> 0, true -> -1 aka !0 + // This is branchless code to select one or the other pointer. + // However, it drops provenance when transmuting to TwoPtrs, so this is UB. + let val = unsafe { + transmute::<_, &str>( + //~^ ERROR: constructing invalid value: encountered a dangling reference + !mask & transmute::<_, TwoPtrs>("false !") + | mask & transmute::<_, TwoPtrs>("true !"), + ) + }; + println!("{}", val); + } +} diff --git a/src/tools/miri/tests/fail/branchless-select-i128-pointer.stderr b/src/tools/miri/tests/fail/branchless-select-i128-pointer.stderr new file mode 100644 index 0000000000000..96f2ff3282c82 --- /dev/null +++ b/src/tools/miri/tests/fail/branchless-select-i128-pointer.stderr @@ -0,0 +1,19 @@ +error: Undefined Behavior: constructing invalid value: encountered a dangling reference (address $HEX is unallocated) + --> $DIR/branchless-select-i128-pointer.rs:LL:CC + | +LL | / transmute::<_, &str>( +LL | | +LL | | !mask & transmute::<_, TwoPtrs>("false !") +LL | | | mask & transmute::<_, TwoPtrs>("true !"), +LL | | ) + | |_____________^ constructing invalid value: encountered a dangling reference (address $HEX is unallocated) + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/branchless-select-i128-pointer.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/breakpoint.rs b/src/tools/miri/tests/fail/breakpoint.rs new file mode 100644 index 0000000000000..fb1d4d958ee6b --- /dev/null +++ b/src/tools/miri/tests/fail/breakpoint.rs @@ -0,0 +1,7 @@ +#![feature(core_intrinsics)] + +fn main() { + unsafe { + core::intrinsics::breakpoint() //~ ERROR: Trace/breakpoint trap + }; +} diff --git a/src/tools/miri/tests/fail/breakpoint.stderr b/src/tools/miri/tests/fail/breakpoint.stderr new file mode 100644 index 0000000000000..7b9bbdb382895 --- /dev/null +++ b/src/tools/miri/tests/fail/breakpoint.stderr @@ -0,0 +1,12 @@ +error: abnormal termination: Trace/breakpoint trap + --> $DIR/breakpoint.rs:LL:CC + | +LL | core::intrinsics::breakpoint() + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Trace/breakpoint trap + | + = note: inside `main` at $DIR/breakpoint.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/concurrency/libc_pthread_create_main_terminate.rs b/src/tools/miri/tests/fail/concurrency/libc_pthread_create_main_terminate.rs new file mode 100644 index 0000000000000..065ad2d725f8f --- /dev/null +++ b/src/tools/miri/tests/fail/concurrency/libc_pthread_create_main_terminate.rs @@ -0,0 +1,19 @@ +//@ignore-target-windows: No libc on Windows +//@error-pattern: the main thread terminated without waiting for all remaining threads + +// Check that we terminate the program when the main thread terminates. + +use std::{mem, ptr}; + +extern "C" fn thread_start(_null: *mut libc::c_void) -> *mut libc::c_void { + loop {} +} + +fn main() { + unsafe { + let mut native: libc::pthread_t = mem::zeroed(); + let attr: libc::pthread_attr_t = mem::zeroed(); + // assert_eq!(libc::pthread_attr_init(&mut attr), 0); FIXME: this function is not yet implemented. + assert_eq!(libc::pthread_create(&mut native, &attr, thread_start, ptr::null_mut()), 0); + } +} diff --git a/src/tools/miri/tests/fail/concurrency/libc_pthread_create_main_terminate.stderr b/src/tools/miri/tests/fail/concurrency/libc_pthread_create_main_terminate.stderr new file mode 100644 index 0000000000000..c5093c0e60113 --- /dev/null +++ b/src/tools/miri/tests/fail/concurrency/libc_pthread_create_main_terminate.stderr @@ -0,0 +1,6 @@ +error: the main thread terminated without waiting for all remaining threads + +note: pass `-Zmiri-ignore-leaks` to disable this check + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/concurrency/libc_pthread_create_too_few_args.rs b/src/tools/miri/tests/fail/concurrency/libc_pthread_create_too_few_args.rs new file mode 100644 index 0000000000000..e1d3704af7c0b --- /dev/null +++ b/src/tools/miri/tests/fail/concurrency/libc_pthread_create_too_few_args.rs @@ -0,0 +1,22 @@ +//@ignore-target-windows: No libc on Windows + +//! The thread function must have exactly one argument. + +use std::{mem, ptr}; + +extern "C" fn thread_start() -> *mut libc::c_void { + panic!() //~ ERROR: callee has fewer arguments than expected +} + +fn main() { + unsafe { + let mut native: libc::pthread_t = mem::zeroed(); + let attr: libc::pthread_attr_t = mem::zeroed(); + // assert_eq!(libc::pthread_attr_init(&mut attr), 0); FIXME: this function is not yet implemented. + let thread_start: extern "C" fn() -> *mut libc::c_void = thread_start; + let thread_start: extern "C" fn(*mut libc::c_void) -> *mut libc::c_void = + mem::transmute(thread_start); + assert_eq!(libc::pthread_create(&mut native, &attr, thread_start, ptr::null_mut()), 0); + assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0); + } +} diff --git a/src/tools/miri/tests/fail/concurrency/libc_pthread_create_too_few_args.stderr b/src/tools/miri/tests/fail/concurrency/libc_pthread_create_too_few_args.stderr new file mode 100644 index 0000000000000..94463bef8f0fe --- /dev/null +++ b/src/tools/miri/tests/fail/concurrency/libc_pthread_create_too_few_args.stderr @@ -0,0 +1,14 @@ +error: Undefined Behavior: callee has fewer arguments than expected + --> $DIR/libc_pthread_create_too_few_args.rs:LL:CC + | +LL | panic!() + | ^^^^^^^^ callee has fewer arguments than expected + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `thread_start` at RUSTLIB/std/src/panic.rs:LL:CC + = note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/concurrency/libc_pthread_create_too_many_args.rs b/src/tools/miri/tests/fail/concurrency/libc_pthread_create_too_many_args.rs new file mode 100644 index 0000000000000..7408634db528f --- /dev/null +++ b/src/tools/miri/tests/fail/concurrency/libc_pthread_create_too_many_args.rs @@ -0,0 +1,22 @@ +//@ignore-target-windows: No libc on Windows + +//! The thread function must have exactly one argument. + +use std::{mem, ptr}; + +extern "C" fn thread_start(_null: *mut libc::c_void, _x: i32) -> *mut libc::c_void { + panic!() //~ ERROR: callee has more arguments than expected +} + +fn main() { + unsafe { + let mut native: libc::pthread_t = mem::zeroed(); + let attr: libc::pthread_attr_t = mem::zeroed(); + // assert_eq!(libc::pthread_attr_init(&mut attr), 0); FIXME: this function is not yet implemented. + let thread_start: extern "C" fn(*mut libc::c_void, i32) -> *mut libc::c_void = thread_start; + let thread_start: extern "C" fn(*mut libc::c_void) -> *mut libc::c_void = + mem::transmute(thread_start); + assert_eq!(libc::pthread_create(&mut native, &attr, thread_start, ptr::null_mut()), 0); + assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0); + } +} diff --git a/src/tools/miri/tests/fail/concurrency/libc_pthread_create_too_many_args.stderr b/src/tools/miri/tests/fail/concurrency/libc_pthread_create_too_many_args.stderr new file mode 100644 index 0000000000000..fdbe91cc8a803 --- /dev/null +++ b/src/tools/miri/tests/fail/concurrency/libc_pthread_create_too_many_args.stderr @@ -0,0 +1,14 @@ +error: Undefined Behavior: callee has more arguments than expected + --> $DIR/libc_pthread_create_too_many_args.rs:LL:CC + | +LL | panic!() + | ^^^^^^^^ callee has more arguments than expected + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `thread_start` at RUSTLIB/std/src/panic.rs:LL:CC + = note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/concurrency/libc_pthread_join_detached.rs b/src/tools/miri/tests/fail/concurrency/libc_pthread_join_detached.rs new file mode 100644 index 0000000000000..0b810dc8c7212 --- /dev/null +++ b/src/tools/miri/tests/fail/concurrency/libc_pthread_join_detached.rs @@ -0,0 +1,20 @@ +//@ignore-target-windows: No libc on Windows + +// Joining a detached thread is undefined behavior. + +use std::{mem, ptr}; + +extern "C" fn thread_start(_null: *mut libc::c_void) -> *mut libc::c_void { + ptr::null_mut() +} + +fn main() { + unsafe { + let mut native: libc::pthread_t = mem::zeroed(); + let attr: libc::pthread_attr_t = mem::zeroed(); + // assert_eq!(libc::pthread_attr_init(&mut attr), 0); FIXME: this function is not yet implemented. + assert_eq!(libc::pthread_create(&mut native, &attr, thread_start, ptr::null_mut()), 0); + assert_eq!(libc::pthread_detach(native), 0); + assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0); //~ ERROR: Undefined Behavior: trying to join a detached thread + } +} diff --git a/src/tools/miri/tests/fail/concurrency/libc_pthread_join_detached.stderr b/src/tools/miri/tests/fail/concurrency/libc_pthread_join_detached.stderr new file mode 100644 index 0000000000000..763e0d3665d8f --- /dev/null +++ b/src/tools/miri/tests/fail/concurrency/libc_pthread_join_detached.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: trying to join a detached thread + --> $DIR/libc_pthread_join_detached.rs:LL:CC + | +LL | assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trying to join a detached thread + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/libc_pthread_join_detached.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/concurrency/libc_pthread_join_joined.rs b/src/tools/miri/tests/fail/concurrency/libc_pthread_join_joined.rs new file mode 100644 index 0000000000000..04ca4bbb3f611 --- /dev/null +++ b/src/tools/miri/tests/fail/concurrency/libc_pthread_join_joined.rs @@ -0,0 +1,20 @@ +//@ignore-target-windows: No libc on Windows + +// Joining an already joined thread is undefined behavior. + +use std::{mem, ptr}; + +extern "C" fn thread_start(_null: *mut libc::c_void) -> *mut libc::c_void { + ptr::null_mut() +} + +fn main() { + unsafe { + let mut native: libc::pthread_t = mem::zeroed(); + let attr: libc::pthread_attr_t = mem::zeroed(); + // assert_eq!(libc::pthread_attr_init(&mut attr), 0); FIXME: this function is not yet implemented. + assert_eq!(libc::pthread_create(&mut native, &attr, thread_start, ptr::null_mut()), 0); + assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0); + assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0); //~ ERROR: Undefined Behavior: trying to join an already joined thread + } +} diff --git a/src/tools/miri/tests/fail/concurrency/libc_pthread_join_joined.stderr b/src/tools/miri/tests/fail/concurrency/libc_pthread_join_joined.stderr new file mode 100644 index 0000000000000..a3253e2ef933b --- /dev/null +++ b/src/tools/miri/tests/fail/concurrency/libc_pthread_join_joined.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: trying to join an already joined thread + --> $DIR/libc_pthread_join_joined.rs:LL:CC + | +LL | assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trying to join an already joined thread + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/libc_pthread_join_joined.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/concurrency/libc_pthread_join_main.rs b/src/tools/miri/tests/fail/concurrency/libc_pthread_join_main.rs new file mode 100644 index 0000000000000..7576518216372 --- /dev/null +++ b/src/tools/miri/tests/fail/concurrency/libc_pthread_join_main.rs @@ -0,0 +1,16 @@ +//@ignore-target-windows: No libc on Windows + +// Joining the main thread is undefined behavior. + +use std::{ptr, thread}; + +fn main() { + let thread_id: libc::pthread_t = unsafe { libc::pthread_self() }; + let handle = thread::spawn(move || { + unsafe { + assert_eq!(libc::pthread_join(thread_id, ptr::null_mut()), 0); //~ ERROR: Undefined Behavior: trying to join a detached thread + } + }); + thread::yield_now(); + handle.join().unwrap(); +} diff --git a/src/tools/miri/tests/fail/concurrency/libc_pthread_join_main.stderr b/src/tools/miri/tests/fail/concurrency/libc_pthread_join_main.stderr new file mode 100644 index 0000000000000..09e14d46a967f --- /dev/null +++ b/src/tools/miri/tests/fail/concurrency/libc_pthread_join_main.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: trying to join a detached thread + --> $DIR/libc_pthread_join_main.rs:LL:CC + | +LL | assert_eq!(libc::pthread_join(thread_id, ptr::null_mut()), 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trying to join a detached thread + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside closure at $DIR/libc_pthread_join_main.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/concurrency/libc_pthread_join_multiple.rs b/src/tools/miri/tests/fail/concurrency/libc_pthread_join_multiple.rs new file mode 100644 index 0000000000000..966f416eeac7e --- /dev/null +++ b/src/tools/miri/tests/fail/concurrency/libc_pthread_join_multiple.rs @@ -0,0 +1,29 @@ +//@ignore-target-windows: No libc on Windows + +// Joining the same thread from multiple threads is undefined behavior. + +use std::thread; +use std::{mem, ptr}; + +extern "C" fn thread_start(_null: *mut libc::c_void) -> *mut libc::c_void { + // Yield the thread several times so that other threads can join it. + thread::yield_now(); + thread::yield_now(); + ptr::null_mut() +} + +fn main() { + unsafe { + let mut native: libc::pthread_t = mem::zeroed(); + let attr: libc::pthread_attr_t = mem::zeroed(); + // assert_eq!(libc::pthread_attr_init(&mut attr), 0); FIXME: this function is not yet implemented. + assert_eq!(libc::pthread_create(&mut native, &attr, thread_start, ptr::null_mut()), 0); + let mut native_copy: libc::pthread_t = mem::zeroed(); + ptr::copy_nonoverlapping(&native, &mut native_copy, 1); + let handle = thread::spawn(move || { + assert_eq!(libc::pthread_join(native_copy, ptr::null_mut()), 0); //~ ERROR: Undefined Behavior: trying to join an already joined thread + }); + assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0); + handle.join().unwrap(); + } +} diff --git a/src/tools/miri/tests/fail/concurrency/libc_pthread_join_multiple.stderr b/src/tools/miri/tests/fail/concurrency/libc_pthread_join_multiple.stderr new file mode 100644 index 0000000000000..db5d7bfd5daef --- /dev/null +++ b/src/tools/miri/tests/fail/concurrency/libc_pthread_join_multiple.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: trying to join an already joined thread + --> $DIR/libc_pthread_join_multiple.rs:LL:CC + | +LL | ... assert_eq!(libc::pthread_join(native_copy, ptr::null_mut()), 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trying to join an already joined thread + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside closure at $DIR/libc_pthread_join_multiple.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/concurrency/libc_pthread_join_self.rs b/src/tools/miri/tests/fail/concurrency/libc_pthread_join_self.rs new file mode 100644 index 0000000000000..0c25c690f3721 --- /dev/null +++ b/src/tools/miri/tests/fail/concurrency/libc_pthread_join_self.rs @@ -0,0 +1,18 @@ +//@ignore-target-windows: No libc on Windows +// We are making scheduler assumptions here. +//@compile-flags: -Zmiri-preemption-rate=0 + +// Joining itself is undefined behavior. + +use std::{ptr, thread}; + +fn main() { + let handle = thread::spawn(|| { + unsafe { + let native: libc::pthread_t = libc::pthread_self(); + assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0); //~ ERROR: Undefined Behavior: trying to join itself + } + }); + thread::yield_now(); + handle.join().unwrap(); +} diff --git a/src/tools/miri/tests/fail/concurrency/libc_pthread_join_self.stderr b/src/tools/miri/tests/fail/concurrency/libc_pthread_join_self.stderr new file mode 100644 index 0000000000000..8db4a83f9cebb --- /dev/null +++ b/src/tools/miri/tests/fail/concurrency/libc_pthread_join_self.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: trying to join itself + --> $DIR/libc_pthread_join_self.rs:LL:CC + | +LL | assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trying to join itself + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside closure at $DIR/libc_pthread_join_self.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/concurrency/read_only_atomic_cmpxchg.rs b/src/tools/miri/tests/fail/concurrency/read_only_atomic_cmpxchg.rs new file mode 100644 index 0000000000000..cb6aeea665d39 --- /dev/null +++ b/src/tools/miri/tests/fail/concurrency/read_only_atomic_cmpxchg.rs @@ -0,0 +1,11 @@ +// Should not rely on the aliasing model for its failure. +//@compile-flags: -Zmiri-disable-stacked-borrows + +use std::sync::atomic::{AtomicI32, Ordering}; + +fn main() { + static X: i32 = 0; + let x = &X as *const i32 as *const AtomicI32; + let x = unsafe { &*x }; + x.compare_exchange(1, 2, Ordering::Relaxed, Ordering::Relaxed).unwrap_err(); //~ERROR: atomic operations cannot be performed on read-only memory +} diff --git a/src/tools/miri/tests/fail/concurrency/read_only_atomic_cmpxchg.stderr b/src/tools/miri/tests/fail/concurrency/read_only_atomic_cmpxchg.stderr new file mode 100644 index 0000000000000..d51fdee0b256f --- /dev/null +++ b/src/tools/miri/tests/fail/concurrency/read_only_atomic_cmpxchg.stderr @@ -0,0 +1,21 @@ +error: Undefined Behavior: atomic operations cannot be performed on read-only memory + many platforms require atomic read-modify-write instructions to be performed on writeable memory, even if the operation fails (and is hence nominally read-only) + some platforms implement (some) atomic loads via compare-exchange, which means they do not work on read-only memory; it is possible that we could have an exception permitting this for specific kinds of loads + please report an issue at if this is a problem for you + --> $DIR/read_only_atomic_cmpxchg.rs:LL:CC + | +LL | x.compare_exchange(1, 2, Ordering::Relaxed, Ordering::Relaxed).unwrap_err(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ atomic operations cannot be performed on read-only memory +many platforms require atomic read-modify-write instructions to be performed on writeable memory, even if the operation fails (and is hence nominally read-only) +some platforms implement (some) atomic loads via compare-exchange, which means they do not work on read-only memory; it is possible that we could have an exception permitting this for specific kinds of loads +please report an issue at if this is a problem for you + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/read_only_atomic_cmpxchg.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/concurrency/read_only_atomic_load.rs b/src/tools/miri/tests/fail/concurrency/read_only_atomic_load.rs new file mode 100644 index 0000000000000..6e92453e3c195 --- /dev/null +++ b/src/tools/miri/tests/fail/concurrency/read_only_atomic_load.rs @@ -0,0 +1,13 @@ +// Should not rely on the aliasing model for its failure. +//@compile-flags: -Zmiri-disable-stacked-borrows + +use std::sync::atomic::{AtomicI32, Ordering}; + +fn main() { + static X: i32 = 0; + let x = &X as *const i32 as *const AtomicI32; + let x = unsafe { &*x }; + // Some targets can implement atomic loads via compare_exchange, so we cannot allow them on + // read-only memory. + x.load(Ordering::Relaxed); //~ERROR: atomic operations cannot be performed on read-only memory +} diff --git a/src/tools/miri/tests/fail/concurrency/read_only_atomic_load.stderr b/src/tools/miri/tests/fail/concurrency/read_only_atomic_load.stderr new file mode 100644 index 0000000000000..17851d6b470b4 --- /dev/null +++ b/src/tools/miri/tests/fail/concurrency/read_only_atomic_load.stderr @@ -0,0 +1,21 @@ +error: Undefined Behavior: atomic operations cannot be performed on read-only memory + many platforms require atomic read-modify-write instructions to be performed on writeable memory, even if the operation fails (and is hence nominally read-only) + some platforms implement (some) atomic loads via compare-exchange, which means they do not work on read-only memory; it is possible that we could have an exception permitting this for specific kinds of loads + please report an issue at if this is a problem for you + --> $DIR/read_only_atomic_load.rs:LL:CC + | +LL | x.load(Ordering::Relaxed); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ atomic operations cannot be performed on read-only memory +many platforms require atomic read-modify-write instructions to be performed on writeable memory, even if the operation fails (and is hence nominally read-only) +some platforms implement (some) atomic loads via compare-exchange, which means they do not work on read-only memory; it is possible that we could have an exception permitting this for specific kinds of loads +please report an issue at if this is a problem for you + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/read_only_atomic_load.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/concurrency/thread_local_static_dealloc.rs b/src/tools/miri/tests/fail/concurrency/thread_local_static_dealloc.rs new file mode 100644 index 0000000000000..d89c670b632e6 --- /dev/null +++ b/src/tools/miri/tests/fail/concurrency/thread_local_static_dealloc.rs @@ -0,0 +1,16 @@ +//! Ensure that thread-local statics get deallocated when the thread dies. + +#![feature(thread_local)] + +#[thread_local] +static mut TLS: u8 = 0; + +struct SendRaw(*const u8); +unsafe impl Send for SendRaw {} + +fn main() { + unsafe { + let dangling_ptr = std::thread::spawn(|| SendRaw(&TLS as *const u8)).join().unwrap(); + let _val = *dangling_ptr.0; //~ ERROR: dereferenced after this allocation got freed + } +} diff --git a/src/tools/miri/tests/fail/concurrency/thread_local_static_dealloc.stderr b/src/tools/miri/tests/fail/concurrency/thread_local_static_dealloc.stderr new file mode 100644 index 0000000000000..cc3e56398781b --- /dev/null +++ b/src/tools/miri/tests/fail/concurrency/thread_local_static_dealloc.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed + --> $DIR/thread_local_static_dealloc.rs:LL:CC + | +LL | let _val = *dangling_ptr.0; + | ^^^^^^^^^^^^^^^ pointer to ALLOC was dereferenced after this allocation got freed + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/thread_local_static_dealloc.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/concurrency/unwind_top_of_stack.rs b/src/tools/miri/tests/fail/concurrency/unwind_top_of_stack.rs new file mode 100644 index 0000000000000..4704cfed03938 --- /dev/null +++ b/src/tools/miri/tests/fail/concurrency/unwind_top_of_stack.rs @@ -0,0 +1,29 @@ +//@ignore-target-windows: No libc on Windows + +//@compile-flags: -Zmiri-disable-abi-check + +//! Unwinding past the top frame of a stack is Undefined Behavior. + +#![feature(c_unwind)] + +use std::{mem, ptr}; + +extern "C-unwind" fn thread_start(_null: *mut libc::c_void) -> *mut libc::c_void { + //~^ ERROR: unwinding past the topmost frame of the stack + panic!() +} + +fn main() { + unsafe { + let mut native: libc::pthread_t = mem::zeroed(); + let attr: libc::pthread_attr_t = mem::zeroed(); + // assert_eq!(libc::pthread_attr_init(&mut attr), 0); FIXME: this function is not yet implemented. + // Cast to avoid inserting abort-on-unwind. + let thread_start: extern "C-unwind" fn(*mut libc::c_void) -> *mut libc::c_void = + thread_start; + let thread_start: extern "C" fn(*mut libc::c_void) -> *mut libc::c_void = + mem::transmute(thread_start); + assert_eq!(libc::pthread_create(&mut native, &attr, thread_start, ptr::null_mut()), 0); + assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0); + } +} diff --git a/src/tools/miri/tests/fail/concurrency/unwind_top_of_stack.stderr b/src/tools/miri/tests/fail/concurrency/unwind_top_of_stack.stderr new file mode 100644 index 0000000000000..98db33e3206bd --- /dev/null +++ b/src/tools/miri/tests/fail/concurrency/unwind_top_of_stack.stderr @@ -0,0 +1,18 @@ +thread '' panicked at 'explicit panic', $DIR/unwind_top_of_stack.rs:LL:CC +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +error: Undefined Behavior: unwinding past the topmost frame of the stack + --> $DIR/unwind_top_of_stack.rs:LL:CC + | +LL | / extern "C-unwind" fn thread_start(_null: *mut libc::c_void) -> *mut libc::c_void { +LL | | +LL | | panic!() +LL | | } + | |_^ unwinding past the topmost frame of the stack + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `thread_start` at $DIR/unwind_top_of_stack.rs:LL:CC + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/concurrency/windows_join_detached.rs b/src/tools/miri/tests/fail/concurrency/windows_join_detached.rs new file mode 100644 index 0000000000000..548ed63534dbd --- /dev/null +++ b/src/tools/miri/tests/fail/concurrency/windows_join_detached.rs @@ -0,0 +1,21 @@ +//@only-target-windows: Uses win32 api functions +//@error-pattern: Undefined Behavior: trying to join a detached thread + +// Joining a detached thread is undefined behavior. + +use std::os::windows::io::{AsRawHandle, RawHandle}; +use std::thread; + +extern "system" { + fn CloseHandle(handle: RawHandle) -> u32; +} + +fn main() { + let thread = thread::spawn(|| ()); + + unsafe { + assert_ne!(CloseHandle(thread.as_raw_handle()), 0); + } + + thread.join().unwrap(); +} diff --git a/src/tools/miri/tests/fail/concurrency/windows_join_detached.stderr b/src/tools/miri/tests/fail/concurrency/windows_join_detached.stderr new file mode 100644 index 0000000000000..78c75611d3333 --- /dev/null +++ b/src/tools/miri/tests/fail/concurrency/windows_join_detached.stderr @@ -0,0 +1,22 @@ +error: Undefined Behavior: trying to join a detached thread + --> RUSTLIB/std/src/sys/PLATFORM/thread.rs:LL:CC + | +LL | let rc = unsafe { c::WaitForSingleObject(self.handle.as_raw_handle(), c::INFINITE) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trying to join a detached thread + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `std::sys::PLATFORM::thread::Thread::join` at RUSTLIB/std/src/sys/PLATFORM/thread.rs:LL:CC + = note: inside `std::thread::JoinInner::<()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC + = note: inside `std::thread::JoinHandle::<()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC +note: inside `main` at $DIR/windows_join_detached.rs:LL:CC + --> $DIR/windows_join_detached.rs:LL:CC + | +LL | thread.join().unwrap(); + | ^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/concurrency/windows_join_main.rs b/src/tools/miri/tests/fail/concurrency/windows_join_main.rs new file mode 100644 index 0000000000000..cde6d19ef25bb --- /dev/null +++ b/src/tools/miri/tests/fail/concurrency/windows_join_main.rs @@ -0,0 +1,28 @@ +//@only-target-windows: Uses win32 api functions +// We are making scheduler assumptions here. +//@compile-flags: -Zmiri-preemption-rate=0 + +// On windows, joining main is not UB, but it will block a thread forever. + +use std::thread; + +extern "system" { + fn WaitForSingleObject(handle: isize, timeout: u32) -> u32; +} + +const INFINITE: u32 = u32::MAX; + +// XXX HACK: This is how miri represents the handle for thread 0. +// This value can be "legitimately" obtained by using `GetCurrentThread` with `DuplicateHandle` +// but miri does not implement `DuplicateHandle` yet. +const MAIN_THREAD: isize = (2i32 << 30) as isize; + +fn main() { + thread::spawn(|| { + unsafe { + assert_eq!(WaitForSingleObject(MAIN_THREAD, INFINITE), 0); //~ ERROR: deadlock: the evaluated program deadlocked + } + }) + .join() + .unwrap(); +} diff --git a/src/tools/miri/tests/fail/concurrency/windows_join_main.stderr b/src/tools/miri/tests/fail/concurrency/windows_join_main.stderr new file mode 100644 index 0000000000000..ff0d074fa7d26 --- /dev/null +++ b/src/tools/miri/tests/fail/concurrency/windows_join_main.stderr @@ -0,0 +1,13 @@ +error: deadlock: the evaluated program deadlocked + --> $DIR/windows_join_main.rs:LL:CC + | +LL | assert_eq!(WaitForSingleObject(MAIN_THREAD, INFINITE), 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program deadlocked + | + = note: inside closure at RUSTLIB/core/src/macros/mod.rs:LL:CC + = note: this error originates in the macro `assert_eq` (in Nightly builds, run with -Z macro-backtrace for more info) + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/concurrency/windows_join_self.rs b/src/tools/miri/tests/fail/concurrency/windows_join_self.rs new file mode 100644 index 0000000000000..d9bbf66a7dca5 --- /dev/null +++ b/src/tools/miri/tests/fail/concurrency/windows_join_self.rs @@ -0,0 +1,25 @@ +//@only-target-windows: Uses win32 api functions +// We are making scheduler assumptions here. +//@compile-flags: -Zmiri-preemption-rate=0 + +// On windows, a thread joining itself is not UB, but it will deadlock. + +use std::thread; + +extern "system" { + fn GetCurrentThread() -> usize; + fn WaitForSingleObject(handle: usize, timeout: u32) -> u32; +} + +const INFINITE: u32 = u32::MAX; + +fn main() { + thread::spawn(|| { + unsafe { + let native = GetCurrentThread(); + assert_eq!(WaitForSingleObject(native, INFINITE), 0); //~ ERROR: deadlock: the evaluated program deadlocked + } + }) + .join() + .unwrap(); +} diff --git a/src/tools/miri/tests/fail/concurrency/windows_join_self.stderr b/src/tools/miri/tests/fail/concurrency/windows_join_self.stderr new file mode 100644 index 0000000000000..bbec3f7257ec0 --- /dev/null +++ b/src/tools/miri/tests/fail/concurrency/windows_join_self.stderr @@ -0,0 +1,12 @@ +error: deadlock: the evaluated program deadlocked + --> $DIR/windows_join_self.rs:LL:CC + | +LL | assert_eq!(WaitForSingleObject(native, INFINITE), 0); + | ^ the evaluated program deadlocked + | + = note: inside closure at $DIR/windows_join_self.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/const-ub-checks.rs b/src/tools/miri/tests/fail/const-ub-checks.rs new file mode 100644 index 0000000000000..fa522c30cbd0f --- /dev/null +++ b/src/tools/miri/tests/fail/const-ub-checks.rs @@ -0,0 +1,11 @@ +#![feature(const_ptr_read)] + +const UNALIGNED_READ: () = unsafe { + let x = &[0u8; 4]; + let ptr = x.as_ptr().cast::(); + ptr.read(); //~ERROR: evaluation of constant value failed +}; + +fn main() { + let _x = UNALIGNED_READ; +} diff --git a/src/tools/miri/tests/fail/const-ub-checks.stderr b/src/tools/miri/tests/fail/const-ub-checks.stderr new file mode 100644 index 0000000000000..a8b7ea242b970 --- /dev/null +++ b/src/tools/miri/tests/fail/const-ub-checks.stderr @@ -0,0 +1,9 @@ +error[E0080]: evaluation of constant value failed + --> $DIR/const-ub-checks.rs:LL:CC + | +LL | ptr.read(); + | ^^^^^^^^^^ accessing memory with alignment ALIGN, but alignment ALIGN is required + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0080`. diff --git a/src/tools/miri/tests/fail/copy_half_a_pointer.rs b/src/tools/miri/tests/fail/copy_half_a_pointer.rs new file mode 100644 index 0000000000000..e1dcdda7fdfe3 --- /dev/null +++ b/src/tools/miri/tests/fail/copy_half_a_pointer.rs @@ -0,0 +1,21 @@ +//@normalize-stderr-test: "\+0x[48]" -> "+HALF_PTR" +#![allow(dead_code)] + +// We use packed structs to get around alignment restrictions +#[repr(packed)] +struct Data { + pad: u8, + ptr: &'static i32, +} + +static G: i32 = 0; + +fn main() { + let mut d = Data { pad: 0, ptr: &G }; + + // Get a pointer to the beginning of the Data struct (one u8 byte, then the pointer bytes). + let d_alias = &mut d as *mut _ as *mut *const u8; + unsafe { + let _x = d_alias.read_unaligned(); //~ERROR: unable to copy parts of a pointer + } +} diff --git a/src/tools/miri/tests/fail/copy_half_a_pointer.stderr b/src/tools/miri/tests/fail/copy_half_a_pointer.stderr new file mode 100644 index 0000000000000..21797757084ee --- /dev/null +++ b/src/tools/miri/tests/fail/copy_half_a_pointer.stderr @@ -0,0 +1,14 @@ +error: unsupported operation: unable to copy parts of a pointer from memory at ALLOC+HALF_PTR + --> $DIR/copy_half_a_pointer.rs:LL:CC + | +LL | let _x = d_alias.read_unaligned(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ unable to copy parts of a pointer from memory at ALLOC+HALF_PTR + | + = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support + = note: BACKTRACE: + = note: inside `main` at $DIR/copy_half_a_pointer.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/crates/tokio_mvp.rs b/src/tools/miri/tests/fail/crates/tokio_mvp.rs new file mode 100644 index 0000000000000..7cb42c09a9677 --- /dev/null +++ b/src/tools/miri/tests/fail/crates/tokio_mvp.rs @@ -0,0 +1,7 @@ +//@compile-flags: -Zmiri-disable-isolation +//@error-pattern: can't call foreign function: epoll_create1 +//@normalize-stderr-test: " = note: inside .*\n" -> "" +//@only-target-linux: the errors differ too much between platforms + +#[tokio::main] +async fn main() {} diff --git a/src/tools/miri/tests/fail/crates/tokio_mvp.stderr b/src/tools/miri/tests/fail/crates/tokio_mvp.stderr new file mode 100644 index 0000000000000..016081d90adf7 --- /dev/null +++ b/src/tools/miri/tests/fail/crates/tokio_mvp.stderr @@ -0,0 +1,19 @@ +error: unsupported operation: can't call foreign function: epoll_create1 + --> CARGO_REGISTRY/.../epoll.rs:LL:CC + | +LL | syscall!(epoll_create1(flag)).map(|ep| Selector { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ can't call foreign function: epoll_create1 + | + = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support + = note: BACKTRACE: +note: inside `main` at $DIR/tokio_mvp.rs:LL:CC + --> $DIR/tokio_mvp.rs:LL:CC + | +LL | #[tokio::main] + | ^^^^^^^^^^^^^^ + = note: this error originates in the macro `syscall` which comes from the expansion of the attribute macro `tokio::main` (in Nightly builds, run with -Z macro-backtrace for more info) + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_addr_of.rs b/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_addr_of.rs new file mode 100644 index 0000000000000..4249c1cbf0177 --- /dev/null +++ b/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_addr_of.rs @@ -0,0 +1,12 @@ +// Make sure we find these even with many checks disabled. +//@compile-flags: -Zmiri-disable-alignment-check -Zmiri-disable-stacked-borrows -Zmiri-disable-validation +use std::ptr; + +fn main() { + let p = { + let b = Box::new(42); + &*b as *const i32 + }; + let x = unsafe { ptr::addr_of!(*p) }; //~ ERROR: dereferenced after this allocation got freed + panic!("this should never print: {:?}", x); +} diff --git a/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_addr_of.stderr b/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_addr_of.stderr new file mode 100644 index 0000000000000..5f081afe68af8 --- /dev/null +++ b/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_addr_of.stderr @@ -0,0 +1,16 @@ +error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed + --> $DIR/dangling_pointer_addr_of.rs:LL:CC + | +LL | let x = unsafe { ptr::addr_of!(*p) }; + | ^^^^^^^^^^^^^^^^^ pointer to ALLOC was dereferenced after this allocation got freed + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at RUSTLIB/core/src/ptr/mod.rs:LL:CC + = note: this error originates in the macro `ptr::addr_of` (in Nightly builds, run with -Z macro-backtrace for more info) + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_deref.rs b/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_deref.rs new file mode 100644 index 0000000000000..ad2a599b60b48 --- /dev/null +++ b/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_deref.rs @@ -0,0 +1,11 @@ +// Make sure we find these even with many checks disabled. +//@compile-flags: -Zmiri-disable-alignment-check -Zmiri-disable-stacked-borrows -Zmiri-disable-validation + +fn main() { + let p = { + let b = Box::new(42); + &*b as *const i32 + }; + let x = unsafe { *p }; //~ ERROR: dereferenced after this allocation got freed + panic!("this should never print: {}", x); +} diff --git a/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_deref.stderr b/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_deref.stderr new file mode 100644 index 0000000000000..cb323818845df --- /dev/null +++ b/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_deref.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed + --> $DIR/dangling_pointer_deref.rs:LL:CC + | +LL | let x = unsafe { *p }; + | ^^ pointer to ALLOC was dereferenced after this allocation got freed + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/dangling_pointer_deref.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/dangling_pointers/dangling_zst_deref.rs b/src/tools/miri/tests/fail/dangling_pointers/dangling_zst_deref.rs new file mode 100644 index 0000000000000..534d7d5f42f32 --- /dev/null +++ b/src/tools/miri/tests/fail/dangling_pointers/dangling_zst_deref.rs @@ -0,0 +1,11 @@ +// Make sure we find these even with many checks disabled. +// Some optimizations remove ZST accesses, thus masking this UB. +//@compile-flags: -Zmir-opt-level=0 -Zmiri-disable-alignment-check -Zmiri-disable-stacked-borrows -Zmiri-disable-validation + +fn main() { + let p = { + let b = Box::new(42); + &*b as *const i32 as *const () + }; + let _x = unsafe { *p }; //~ ERROR: dereferenced after this allocation got freed +} diff --git a/src/tools/miri/tests/fail/dangling_pointers/dangling_zst_deref.stderr b/src/tools/miri/tests/fail/dangling_pointers/dangling_zst_deref.stderr new file mode 100644 index 0000000000000..02db6302a0a1e --- /dev/null +++ b/src/tools/miri/tests/fail/dangling_pointers/dangling_zst_deref.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed + --> $DIR/dangling_zst_deref.rs:LL:CC + | +LL | let _x = unsafe { *p }; + | ^^ pointer to ALLOC was dereferenced after this allocation got freed + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/dangling_zst_deref.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/dangling_pointers/deref-invalid-ptr.rs b/src/tools/miri/tests/fail/dangling_pointers/deref-invalid-ptr.rs new file mode 100644 index 0000000000000..57e95ef19dc99 --- /dev/null +++ b/src/tools/miri/tests/fail/dangling_pointers/deref-invalid-ptr.rs @@ -0,0 +1,7 @@ +// This should fail even without validation. +//@compile-flags: -Zmiri-disable-validation -Zmiri-permissive-provenance + +fn main() { + let x = 16usize as *const u32; + let _y = unsafe { &*x as *const u32 }; //~ ERROR: is a dangling pointer +} diff --git a/src/tools/miri/tests/fail/dangling_pointers/deref-invalid-ptr.stderr b/src/tools/miri/tests/fail/dangling_pointers/deref-invalid-ptr.stderr new file mode 100644 index 0000000000000..3e2c3903b7e47 --- /dev/null +++ b/src/tools/miri/tests/fail/dangling_pointers/deref-invalid-ptr.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: dereferencing pointer failed: 0x10[noalloc] is a dangling pointer (it has no provenance) + --> $DIR/deref-invalid-ptr.rs:LL:CC + | +LL | let _y = unsafe { &*x as *const u32 }; + | ^^^ dereferencing pointer failed: 0x10[noalloc] is a dangling pointer (it has no provenance) + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/deref-invalid-ptr.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/dangling_pointers/deref-partially-dangling.rs b/src/tools/miri/tests/fail/dangling_pointers/deref-partially-dangling.rs new file mode 100644 index 0000000000000..27040c26dc212 --- /dev/null +++ b/src/tools/miri/tests/fail/dangling_pointers/deref-partially-dangling.rs @@ -0,0 +1,8 @@ +// Deref a raw ptr to access a field of a large struct, where the field +// is allocated but not the entire struct is. +fn main() { + let x = (1, 13); + let xptr = &x as *const _ as *const (i32, i32, i32); + let val = unsafe { (*xptr).1 }; //~ ERROR: pointer to 12 bytes starting at offset 0 is out-of-bounds + assert_eq!(val, 13); +} diff --git a/src/tools/miri/tests/fail/dangling_pointers/deref-partially-dangling.stderr b/src/tools/miri/tests/fail/dangling_pointers/deref-partially-dangling.stderr new file mode 100644 index 0000000000000..fe039ef3adaf9 --- /dev/null +++ b/src/tools/miri/tests/fail/dangling_pointers/deref-partially-dangling.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: dereferencing pointer failed: ALLOC has size 8, so pointer to 12 bytes starting at offset 0 is out-of-bounds + --> $DIR/deref-partially-dangling.rs:LL:CC + | +LL | let val = unsafe { (*xptr).1 }; + | ^^^^^^^^^ dereferencing pointer failed: ALLOC has size 8, so pointer to 12 bytes starting at offset 0 is out-of-bounds + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/deref-partially-dangling.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/dangling_pointers/dyn_size.rs b/src/tools/miri/tests/fail/dangling_pointers/dyn_size.rs new file mode 100644 index 0000000000000..54f353ebebeb1 --- /dev/null +++ b/src/tools/miri/tests/fail/dangling_pointers/dyn_size.rs @@ -0,0 +1,13 @@ +// should find the bug even without these, but gets masked by optimizations +//@compile-flags: -Zmiri-disable-validation -Zmiri-disable-stacked-borrows -Zmir-opt-level=0 + +struct SliceWithHead(u8, [u8]); + +fn main() { + let buf = [0u32; 1]; + // We craft a wide pointer `*const SliceWithHead` such that the unsized tail is only partially allocated. + // That should be UB, as the reference is not fully dereferencable. + let ptr: *const SliceWithHead = unsafe { std::mem::transmute((&buf, 4usize)) }; + // Re-borrow that. This should be UB. + let _ptr = unsafe { &*ptr }; //~ ERROR: pointer to 5 bytes starting at offset 0 is out-of-bounds +} diff --git a/src/tools/miri/tests/fail/dangling_pointers/dyn_size.stderr b/src/tools/miri/tests/fail/dangling_pointers/dyn_size.stderr new file mode 100644 index 0000000000000..33aa6c8441017 --- /dev/null +++ b/src/tools/miri/tests/fail/dangling_pointers/dyn_size.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: dereferencing pointer failed: ALLOC has size 4, so pointer to 5 bytes starting at offset 0 is out-of-bounds + --> $DIR/dyn_size.rs:LL:CC + | +LL | let _ptr = unsafe { &*ptr }; + | ^^^^^ dereferencing pointer failed: ALLOC has size 4, so pointer to 5 bytes starting at offset 0 is out-of-bounds + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/dyn_size.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/dangling_pointers/maybe_null_pointer_deref_zst.rs b/src/tools/miri/tests/fail/dangling_pointers/maybe_null_pointer_deref_zst.rs new file mode 100644 index 0000000000000..a48a3189db2e3 --- /dev/null +++ b/src/tools/miri/tests/fail/dangling_pointers/maybe_null_pointer_deref_zst.rs @@ -0,0 +1,8 @@ +// Some optimizations remove ZST accesses, thus masking this UB. +//@compile-flags: -Zmir-opt-level=0 + +fn main() { + // This pointer *could* be NULL so we cannot load from it, not even at ZST + let ptr = (&0u8 as *const u8).wrapping_sub(0x800) as *const (); + let _x: () = unsafe { *ptr }; //~ ERROR: out-of-bounds +} diff --git a/src/tools/miri/tests/fail/dangling_pointers/maybe_null_pointer_deref_zst.stderr b/src/tools/miri/tests/fail/dangling_pointers/maybe_null_pointer_deref_zst.stderr new file mode 100644 index 0000000000000..3e492a170c8b1 --- /dev/null +++ b/src/tools/miri/tests/fail/dangling_pointers/maybe_null_pointer_deref_zst.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: dereferencing pointer failed: ALLOC has size 1, so pointer at offset -2048 is out-of-bounds + --> $DIR/maybe_null_pointer_deref_zst.rs:LL:CC + | +LL | let _x: () = unsafe { *ptr }; + | ^^^^ dereferencing pointer failed: ALLOC has size 1, so pointer at offset -2048 is out-of-bounds + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/maybe_null_pointer_deref_zst.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/dangling_pointers/maybe_null_pointer_write_zst.rs b/src/tools/miri/tests/fail/dangling_pointers/maybe_null_pointer_write_zst.rs new file mode 100644 index 0000000000000..449c65d218a02 --- /dev/null +++ b/src/tools/miri/tests/fail/dangling_pointers/maybe_null_pointer_write_zst.rs @@ -0,0 +1,11 @@ +// Some optimizations remove ZST accesses, thus masking this UB. +//@compile-flags: -Zmir-opt-level=0 + +fn main() { + // This pointer *could* be NULL so we cannot load from it, not even at ZST. + // Not using the () type here, as writes of that type do not even have MIR generated. + // Also not assigning directly as that's array initialization, not assignment. + let zst_val = [1u8; 0]; + let ptr = (&0u8 as *const u8).wrapping_sub(0x800) as *mut [u8; 0]; + unsafe { *ptr = zst_val }; //~ ERROR: out-of-bounds +} diff --git a/src/tools/miri/tests/fail/dangling_pointers/maybe_null_pointer_write_zst.stderr b/src/tools/miri/tests/fail/dangling_pointers/maybe_null_pointer_write_zst.stderr new file mode 100644 index 0000000000000..c41c20aaf4a7b --- /dev/null +++ b/src/tools/miri/tests/fail/dangling_pointers/maybe_null_pointer_write_zst.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: dereferencing pointer failed: ALLOC has size 1, so pointer at offset -2048 is out-of-bounds + --> $DIR/maybe_null_pointer_write_zst.rs:LL:CC + | +LL | unsafe { *ptr = zst_val }; + | ^^^^^^^^^^^^^^ dereferencing pointer failed: ALLOC has size 1, so pointer at offset -2048 is out-of-bounds + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/maybe_null_pointer_write_zst.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/dangling_pointers/null_pointer_deref.rs b/src/tools/miri/tests/fail/dangling_pointers/null_pointer_deref.rs new file mode 100644 index 0000000000000..a0773c63cf6bf --- /dev/null +++ b/src/tools/miri/tests/fail/dangling_pointers/null_pointer_deref.rs @@ -0,0 +1,5 @@ +#[allow(deref_nullptr)] +fn main() { + let x: i32 = unsafe { *std::ptr::null() }; //~ ERROR: null pointer is a dangling pointer + panic!("this should never print: {}", x); +} diff --git a/src/tools/miri/tests/fail/dangling_pointers/null_pointer_deref.stderr b/src/tools/miri/tests/fail/dangling_pointers/null_pointer_deref.stderr new file mode 100644 index 0000000000000..64dcaa4548476 --- /dev/null +++ b/src/tools/miri/tests/fail/dangling_pointers/null_pointer_deref.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: dereferencing pointer failed: null pointer is a dangling pointer (it has no provenance) + --> $DIR/null_pointer_deref.rs:LL:CC + | +LL | let x: i32 = unsafe { *std::ptr::null() }; + | ^^^^^^^^^^^^^^^^^ dereferencing pointer failed: null pointer is a dangling pointer (it has no provenance) + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/null_pointer_deref.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/dangling_pointers/null_pointer_deref_zst.rs b/src/tools/miri/tests/fail/dangling_pointers/null_pointer_deref_zst.rs new file mode 100644 index 0000000000000..d6a607c61cbeb --- /dev/null +++ b/src/tools/miri/tests/fail/dangling_pointers/null_pointer_deref_zst.rs @@ -0,0 +1,8 @@ +// Some optimizations remove ZST accesses, thus masking this UB. +//@compile-flags: -Zmir-opt-level=0 + +#[allow(deref_nullptr)] +fn main() { + let x: () = unsafe { *std::ptr::null() }; //~ ERROR: dereferencing pointer failed: null pointer is a dangling pointer + panic!("this should never print: {:?}", x); +} diff --git a/src/tools/miri/tests/fail/dangling_pointers/null_pointer_deref_zst.stderr b/src/tools/miri/tests/fail/dangling_pointers/null_pointer_deref_zst.stderr new file mode 100644 index 0000000000000..301578a4f5fb4 --- /dev/null +++ b/src/tools/miri/tests/fail/dangling_pointers/null_pointer_deref_zst.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: dereferencing pointer failed: null pointer is a dangling pointer (it has no provenance) + --> $DIR/null_pointer_deref_zst.rs:LL:CC + | +LL | let x: () = unsafe { *std::ptr::null() }; + | ^^^^^^^^^^^^^^^^^ dereferencing pointer failed: null pointer is a dangling pointer (it has no provenance) + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/null_pointer_deref_zst.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/dangling_pointers/null_pointer_write.rs b/src/tools/miri/tests/fail/dangling_pointers/null_pointer_write.rs new file mode 100644 index 0000000000000..954596f57542e --- /dev/null +++ b/src/tools/miri/tests/fail/dangling_pointers/null_pointer_write.rs @@ -0,0 +1,4 @@ +#[allow(deref_nullptr)] +fn main() { + unsafe { *std::ptr::null_mut() = 0i32 }; //~ ERROR: null pointer is a dangling pointer +} diff --git a/src/tools/miri/tests/fail/dangling_pointers/null_pointer_write.stderr b/src/tools/miri/tests/fail/dangling_pointers/null_pointer_write.stderr new file mode 100644 index 0000000000000..0e5858a96f9d7 --- /dev/null +++ b/src/tools/miri/tests/fail/dangling_pointers/null_pointer_write.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: dereferencing pointer failed: null pointer is a dangling pointer (it has no provenance) + --> $DIR/null_pointer_write.rs:LL:CC + | +LL | unsafe { *std::ptr::null_mut() = 0i32 }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ dereferencing pointer failed: null pointer is a dangling pointer (it has no provenance) + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/null_pointer_write.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/dangling_pointers/null_pointer_write_zst.rs b/src/tools/miri/tests/fail/dangling_pointers/null_pointer_write_zst.rs new file mode 100644 index 0000000000000..c00344b6de23a --- /dev/null +++ b/src/tools/miri/tests/fail/dangling_pointers/null_pointer_write_zst.rs @@ -0,0 +1,11 @@ +// Some optimizations remove ZST accesses, thus masking this UB. +//@compile-flags: -Zmir-opt-level=0 + +#[allow(deref_nullptr)] +fn main() { + // Not using the () type here, as writes of that type do not even have MIR generated. + // Also not assigning directly as that's array initialization, not assignment. + let zst_val = [1u8; 0]; + unsafe { std::ptr::null_mut::<[u8; 0]>().write(zst_val) }; + //~^ERROR: memory access failed: null pointer is a dangling pointer +} diff --git a/src/tools/miri/tests/fail/dangling_pointers/null_pointer_write_zst.stderr b/src/tools/miri/tests/fail/dangling_pointers/null_pointer_write_zst.stderr new file mode 100644 index 0000000000000..2953d85c25f3f --- /dev/null +++ b/src/tools/miri/tests/fail/dangling_pointers/null_pointer_write_zst.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: memory access failed: null pointer is a dangling pointer (it has no provenance) + --> $DIR/null_pointer_write_zst.rs:LL:CC + | +LL | unsafe { std::ptr::null_mut::<[u8; 0]>().write(zst_val) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ memory access failed: null pointer is a dangling pointer (it has no provenance) + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/null_pointer_write_zst.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/dangling_pointers/out_of_bounds_read1.rs b/src/tools/miri/tests/fail/dangling_pointers/out_of_bounds_read1.rs new file mode 100644 index 0000000000000..58a64eecace8c --- /dev/null +++ b/src/tools/miri/tests/fail/dangling_pointers/out_of_bounds_read1.rs @@ -0,0 +1,5 @@ +fn main() { + let v: Vec = vec![1, 2]; + let x = unsafe { *v.as_ptr().wrapping_offset(5) }; //~ ERROR: out-of-bounds + panic!("this should never print: {}", x); +} diff --git a/src/tools/miri/tests/fail/dangling_pointers/out_of_bounds_read1.stderr b/src/tools/miri/tests/fail/dangling_pointers/out_of_bounds_read1.stderr new file mode 100644 index 0000000000000..b2461ce4230ad --- /dev/null +++ b/src/tools/miri/tests/fail/dangling_pointers/out_of_bounds_read1.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: dereferencing pointer failed: ALLOC has size 2, so pointer to 1 byte starting at offset 5 is out-of-bounds + --> $DIR/out_of_bounds_read1.rs:LL:CC + | +LL | let x = unsafe { *v.as_ptr().wrapping_offset(5) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ dereferencing pointer failed: ALLOC has size 2, so pointer to 1 byte starting at offset 5 is out-of-bounds + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/out_of_bounds_read1.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/dangling_pointers/out_of_bounds_read2.rs b/src/tools/miri/tests/fail/dangling_pointers/out_of_bounds_read2.rs new file mode 100644 index 0000000000000..58a64eecace8c --- /dev/null +++ b/src/tools/miri/tests/fail/dangling_pointers/out_of_bounds_read2.rs @@ -0,0 +1,5 @@ +fn main() { + let v: Vec = vec![1, 2]; + let x = unsafe { *v.as_ptr().wrapping_offset(5) }; //~ ERROR: out-of-bounds + panic!("this should never print: {}", x); +} diff --git a/src/tools/miri/tests/fail/dangling_pointers/out_of_bounds_read2.stderr b/src/tools/miri/tests/fail/dangling_pointers/out_of_bounds_read2.stderr new file mode 100644 index 0000000000000..b17058b406298 --- /dev/null +++ b/src/tools/miri/tests/fail/dangling_pointers/out_of_bounds_read2.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: dereferencing pointer failed: ALLOC has size 2, so pointer to 1 byte starting at offset 5 is out-of-bounds + --> $DIR/out_of_bounds_read2.rs:LL:CC + | +LL | let x = unsafe { *v.as_ptr().wrapping_offset(5) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ dereferencing pointer failed: ALLOC has size 2, so pointer to 1 byte starting at offset 5 is out-of-bounds + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/out_of_bounds_read2.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/dangling_pointers/stack_temporary.rs b/src/tools/miri/tests/fail/dangling_pointers/stack_temporary.rs new file mode 100644 index 0000000000000..1373773f68d59 --- /dev/null +++ b/src/tools/miri/tests/fail/dangling_pointers/stack_temporary.rs @@ -0,0 +1,14 @@ +// This should fail even without validation, but some MIR opts mask the error +//@compile-flags: -Zmiri-disable-validation -Zmir-opt-level=0 + +unsafe fn make_ref<'a>(x: *mut i32) -> &'a mut i32 { + &mut *x +} + +fn main() { + unsafe { + let x = make_ref(&mut 0); // The temporary storing "0" is deallocated at the ";"! + let val = *x; //~ ERROR: dereferenced after this allocation got freed + println!("{}", val); + } +} diff --git a/src/tools/miri/tests/fail/dangling_pointers/stack_temporary.stderr b/src/tools/miri/tests/fail/dangling_pointers/stack_temporary.stderr new file mode 100644 index 0000000000000..679e4809ca663 --- /dev/null +++ b/src/tools/miri/tests/fail/dangling_pointers/stack_temporary.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed + --> $DIR/stack_temporary.rs:LL:CC + | +LL | let val = *x; + | ^^ pointer to ALLOC was dereferenced after this allocation got freed + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/stack_temporary.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/dangling_pointers/storage_dead_dangling.rs b/src/tools/miri/tests/fail/dangling_pointers/storage_dead_dangling.rs new file mode 100644 index 0000000000000..03113585d1464 --- /dev/null +++ b/src/tools/miri/tests/fail/dangling_pointers/storage_dead_dangling.rs @@ -0,0 +1,26 @@ +// This should fail even without validation, but some MIR opts mask the error +//@compile-flags: -Zmiri-disable-validation -Zmir-opt-level=0 -Zmiri-permissive-provenance + +static mut LEAK: usize = 0; + +fn fill(v: &mut i32) { + unsafe { + LEAK = v as *mut _ as usize; + } +} + +fn evil() { + unsafe { &mut *(LEAK as *mut i32) }; //~ ERROR: is a dangling pointer +} + +fn main() { + let _y; + { + let mut x = 0i32; + fill(&mut x); + _y = x; + } + // Now we use a pointer to `x` which is no longer in scope, and thus dead (even though the + // `main` stack frame still exists). We even try going through a `usize` for extra sneakiness! + evil(); +} diff --git a/src/tools/miri/tests/fail/dangling_pointers/storage_dead_dangling.stderr b/src/tools/miri/tests/fail/dangling_pointers/storage_dead_dangling.stderr new file mode 100644 index 0000000000000..72e5f20f924a4 --- /dev/null +++ b/src/tools/miri/tests/fail/dangling_pointers/storage_dead_dangling.stderr @@ -0,0 +1,20 @@ +error: Undefined Behavior: dereferencing pointer failed: $HEX[noalloc] is a dangling pointer (it has no provenance) + --> $DIR/storage_dead_dangling.rs:LL:CC + | +LL | unsafe { &mut *(LEAK as *mut i32) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^ dereferencing pointer failed: $HEX[noalloc] is a dangling pointer (it has no provenance) + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `evil` at $DIR/storage_dead_dangling.rs:LL:CC +note: inside `main` at $DIR/storage_dead_dangling.rs:LL:CC + --> $DIR/storage_dead_dangling.rs:LL:CC + | +LL | evil(); + | ^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/dangling_pointers/wild_pointer_deref.rs b/src/tools/miri/tests/fail/dangling_pointers/wild_pointer_deref.rs new file mode 100644 index 0000000000000..9ffc681465504 --- /dev/null +++ b/src/tools/miri/tests/fail/dangling_pointers/wild_pointer_deref.rs @@ -0,0 +1,7 @@ +//@compile-flags: -Zmiri-permissive-provenance + +fn main() { + let p = 44 as *const i32; + let x = unsafe { *p }; //~ ERROR: is a dangling pointer + panic!("this should never print: {}", x); +} diff --git a/src/tools/miri/tests/fail/dangling_pointers/wild_pointer_deref.stderr b/src/tools/miri/tests/fail/dangling_pointers/wild_pointer_deref.stderr new file mode 100644 index 0000000000000..658fb228174e5 --- /dev/null +++ b/src/tools/miri/tests/fail/dangling_pointers/wild_pointer_deref.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: dereferencing pointer failed: 0x2c[noalloc] is a dangling pointer (it has no provenance) + --> $DIR/wild_pointer_deref.rs:LL:CC + | +LL | let x = unsafe { *p }; + | ^^ dereferencing pointer failed: 0x2c[noalloc] is a dangling pointer (it has no provenance) + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/wild_pointer_deref.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/data_race/alloc_read_race.rs b/src/tools/miri/tests/fail/data_race/alloc_read_race.rs new file mode 100644 index 0000000000000..0bd3068af1ffe --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/alloc_read_race.rs @@ -0,0 +1,49 @@ +//@compile-flags: -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0 +#![feature(new_uninit)] + +use std::mem::MaybeUninit; +use std::ptr::null_mut; +use std::sync::atomic::{AtomicPtr, Ordering}; +use std::thread::spawn; + +#[derive(Copy, Clone)] +struct EvilSend(pub T); + +unsafe impl Send for EvilSend {} +unsafe impl Sync for EvilSend {} + +pub fn main() { + // Shared atomic pointer + let pointer = AtomicPtr::new(null_mut::>()); + let ptr = EvilSend(&pointer as *const AtomicPtr>); + + // Note: this is scheduler-dependent + // the operations need to occur in + // order, otherwise the allocation is + // not visible to the other-thread to + // detect the race: + // 1. alloc + // 2. write + unsafe { + let j1 = spawn(move || { + // Concurrent allocate the memory. + // Uses relaxed semantics to not generate + // a release sequence. + let pointer = &*ptr.0; + pointer.store(Box::into_raw(Box::new_uninit()), Ordering::Relaxed); + }); + + let j2 = spawn(move || { + let pointer = &*ptr.0; + + // Note: could also error due to reading uninitialized memory, but the data-race detector triggers first. + *pointer.load(Ordering::Relaxed) //~ ERROR: Data race detected between Read on thread `` and Allocate on thread `` + }); + + j1.join().unwrap(); + j2.join().unwrap(); + + // Clean up memory, will never be executed + drop(Box::from_raw(pointer.load(Ordering::Relaxed))); + } +} diff --git a/src/tools/miri/tests/fail/data_race/alloc_read_race.stderr b/src/tools/miri/tests/fail/data_race/alloc_read_race.stderr new file mode 100644 index 0000000000000..c6bfd12b24110 --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/alloc_read_race.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: Data race detected between Read on thread `` and Allocate on thread `` at ALLOC + --> $DIR/alloc_read_race.rs:LL:CC + | +LL | *pointer.load(Ordering::Relaxed) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Data race detected between Read on thread `` and Allocate on thread `` at ALLOC + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside closure at $DIR/alloc_read_race.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/data_race/alloc_write_race.rs b/src/tools/miri/tests/fail/data_race/alloc_write_race.rs new file mode 100644 index 0000000000000..7991280721e29 --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/alloc_write_race.rs @@ -0,0 +1,47 @@ +//@compile-flags: -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0 +#![feature(new_uninit)] + +use std::ptr::null_mut; +use std::sync::atomic::{AtomicPtr, Ordering}; +use std::thread::spawn; + +#[derive(Copy, Clone)] +struct EvilSend(pub T); + +unsafe impl Send for EvilSend {} +unsafe impl Sync for EvilSend {} + +pub fn main() { + // Shared atomic pointer + let pointer = AtomicPtr::new(null_mut::()); + let ptr = EvilSend(&pointer as *const AtomicPtr); + + // Note: this is scheduler-dependent + // the operations need to occur in + // order, otherwise the allocation is + // not visible to the other-thread to + // detect the race: + // 1. alloc + // 2. write + unsafe { + let j1 = spawn(move || { + // Concurrent allocate the memory. + // Uses relaxed semantics to not generate + // a release sequence. + let pointer = &*ptr.0; + pointer + .store(Box::into_raw(Box::::new_uninit()) as *mut usize, Ordering::Relaxed); + }); + + let j2 = spawn(move || { + let pointer = &*ptr.0; + *pointer.load(Ordering::Relaxed) = 2; //~ ERROR: Data race detected between Write on thread `` and Allocate on thread `` + }); + + j1.join().unwrap(); + j2.join().unwrap(); + + // Clean up memory, will never be executed + drop(Box::from_raw(pointer.load(Ordering::Relaxed))); + } +} diff --git a/src/tools/miri/tests/fail/data_race/alloc_write_race.stderr b/src/tools/miri/tests/fail/data_race/alloc_write_race.stderr new file mode 100644 index 0000000000000..c4efc175c2077 --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/alloc_write_race.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: Data race detected between Write on thread `` and Allocate on thread `` at ALLOC + --> $DIR/alloc_write_race.rs:LL:CC + | +LL | *pointer.load(Ordering::Relaxed) = 2; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Data race detected between Write on thread `` and Allocate on thread `` at ALLOC + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside closure at $DIR/alloc_write_race.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/data_race/atomic_read_na_write_race1.rs b/src/tools/miri/tests/fail/data_race/atomic_read_na_write_race1.rs new file mode 100644 index 0000000000000..2b0446d724a02 --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/atomic_read_na_write_race1.rs @@ -0,0 +1,29 @@ +// We want to control preemption here. +//@compile-flags: -Zmiri-preemption-rate=0 + +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::thread::spawn; + +#[derive(Copy, Clone)] +struct EvilSend(pub T); + +unsafe impl Send for EvilSend {} +unsafe impl Sync for EvilSend {} + +pub fn main() { + let mut a = AtomicUsize::new(0); + let b = &mut a as *mut AtomicUsize; + let c = EvilSend(b); + unsafe { + let j1 = spawn(move || { + *(c.0 as *mut usize) = 32; + }); + + let j2 = spawn(move || { + (&*c.0).load(Ordering::SeqCst) //~ ERROR: Data race detected between Atomic Load on thread `` and Write on thread `` + }); + + j1.join().unwrap(); + j2.join().unwrap(); + } +} diff --git a/src/tools/miri/tests/fail/data_race/atomic_read_na_write_race1.stderr b/src/tools/miri/tests/fail/data_race/atomic_read_na_write_race1.stderr new file mode 100644 index 0000000000000..04adf0a98b6c5 --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/atomic_read_na_write_race1.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: Data race detected between Atomic Load on thread `` and Write on thread `` at ALLOC + --> $DIR/atomic_read_na_write_race1.rs:LL:CC + | +LL | (&*c.0).load(Ordering::SeqCst) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Data race detected between Atomic Load on thread `` and Write on thread `` at ALLOC + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside closure at $DIR/atomic_read_na_write_race1.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/data_race/atomic_read_na_write_race2.rs b/src/tools/miri/tests/fail/data_race/atomic_read_na_write_race2.rs new file mode 100644 index 0000000000000..ef5157515c64a --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/atomic_read_na_write_race2.rs @@ -0,0 +1,32 @@ +// We want to control preemption here. +//@compile-flags: -Zmiri-preemption-rate=0 + +use std::sync::atomic::AtomicUsize; +use std::sync::atomic::Ordering; +use std::thread::spawn; + +#[derive(Copy, Clone)] +struct EvilSend(pub T); + +unsafe impl Send for EvilSend {} +unsafe impl Sync for EvilSend {} + +pub fn main() { + let mut a = AtomicUsize::new(0); + let b = &mut a as *mut AtomicUsize; + let c = EvilSend(b); + unsafe { + let j1 = spawn(move || { + let atomic_ref = &mut *c.0; + atomic_ref.load(Ordering::SeqCst) + }); + + let j2 = spawn(move || { + let atomic_ref = &mut *c.0; + *atomic_ref.get_mut() = 32; //~ ERROR: Data race detected between Write on thread `` and Atomic Load on thread `` + }); + + j1.join().unwrap(); + j2.join().unwrap(); + } +} diff --git a/src/tools/miri/tests/fail/data_race/atomic_read_na_write_race2.stderr b/src/tools/miri/tests/fail/data_race/atomic_read_na_write_race2.stderr new file mode 100644 index 0000000000000..b48f927b8fcae --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/atomic_read_na_write_race2.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: Data race detected between Write on thread `` and Atomic Load on thread `` at ALLOC + --> $DIR/atomic_read_na_write_race2.rs:LL:CC + | +LL | *atomic_ref.get_mut() = 32; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ Data race detected between Write on thread `` and Atomic Load on thread `` at ALLOC + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside closure at $DIR/atomic_read_na_write_race2.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/data_race/atomic_write_na_read_race1.rs b/src/tools/miri/tests/fail/data_race/atomic_write_na_read_race1.rs new file mode 100644 index 0000000000000..8c17e76748438 --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/atomic_write_na_read_race1.rs @@ -0,0 +1,32 @@ +// We want to control preemption here. +//@compile-flags: -Zmiri-preemption-rate=0 + +use std::sync::atomic::AtomicUsize; +use std::sync::atomic::Ordering; +use std::thread::spawn; + +#[derive(Copy, Clone)] +struct EvilSend(pub T); + +unsafe impl Send for EvilSend {} +unsafe impl Sync for EvilSend {} + +pub fn main() { + let mut a = AtomicUsize::new(0); + let b = &mut a as *mut AtomicUsize; + let c = EvilSend(b); + unsafe { + let j1 = spawn(move || { + let atomic_ref = &mut *c.0; + atomic_ref.store(32, Ordering::SeqCst) + }); + + let j2 = spawn(move || { + let atomic_ref = &mut *c.0; + *atomic_ref.get_mut() //~ ERROR: Data race detected between Read on thread `` and Atomic Store on thread `` + }); + + j1.join().unwrap(); + j2.join().unwrap(); + } +} diff --git a/src/tools/miri/tests/fail/data_race/atomic_write_na_read_race1.stderr b/src/tools/miri/tests/fail/data_race/atomic_write_na_read_race1.stderr new file mode 100644 index 0000000000000..fdb9b353a67bf --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/atomic_write_na_read_race1.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: Data race detected between Read on thread `` and Atomic Store on thread `` at ALLOC + --> $DIR/atomic_write_na_read_race1.rs:LL:CC + | +LL | *atomic_ref.get_mut() + | ^^^^^^^^^^^^^^^^^^^^^ Data race detected between Read on thread `` and Atomic Store on thread `` at ALLOC + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside closure at $DIR/atomic_write_na_read_race1.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/data_race/atomic_write_na_read_race2.rs b/src/tools/miri/tests/fail/data_race/atomic_write_na_read_race2.rs new file mode 100644 index 0000000000000..f14d7c704dbb1 --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/atomic_write_na_read_race2.rs @@ -0,0 +1,29 @@ +// We want to control preemption here. +//@compile-flags: -Zmiri-preemption-rate=0 + +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::thread::spawn; + +#[derive(Copy, Clone)] +struct EvilSend(pub T); + +unsafe impl Send for EvilSend {} +unsafe impl Sync for EvilSend {} + +pub fn main() { + let mut a = AtomicUsize::new(0); + let b = &mut a as *mut AtomicUsize; + let c = EvilSend(b); + unsafe { + let j1 = spawn(move || { + let _val = *(c.0 as *mut usize); + }); + + let j2 = spawn(move || { + (&*c.0).store(32, Ordering::SeqCst); //~ ERROR: Data race detected between Atomic Store on thread `` and Read on thread `` + }); + + j1.join().unwrap(); + j2.join().unwrap(); + } +} diff --git a/src/tools/miri/tests/fail/data_race/atomic_write_na_read_race2.stderr b/src/tools/miri/tests/fail/data_race/atomic_write_na_read_race2.stderr new file mode 100644 index 0000000000000..ec581e322b7d1 --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/atomic_write_na_read_race2.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: Data race detected between Atomic Store on thread `` and Read on thread `` at ALLOC + --> $DIR/atomic_write_na_read_race2.rs:LL:CC + | +LL | (&*c.0).store(32, Ordering::SeqCst); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Data race detected between Atomic Store on thread `` and Read on thread `` at ALLOC + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside closure at $DIR/atomic_write_na_read_race2.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/data_race/atomic_write_na_write_race1.rs b/src/tools/miri/tests/fail/data_race/atomic_write_na_write_race1.rs new file mode 100644 index 0000000000000..0804b33407580 --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/atomic_write_na_write_race1.rs @@ -0,0 +1,29 @@ +// We want to control preemption here. +//@compile-flags: -Zmiri-preemption-rate=0 + +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::thread::spawn; + +#[derive(Copy, Clone)] +struct EvilSend(pub T); + +unsafe impl Send for EvilSend {} +unsafe impl Sync for EvilSend {} + +pub fn main() { + let mut a = AtomicUsize::new(0); + let b = &mut a as *mut AtomicUsize; + let c = EvilSend(b); + unsafe { + let j1 = spawn(move || { + *(c.0 as *mut usize) = 32; + }); + + let j2 = spawn(move || { + (&*c.0).store(64, Ordering::SeqCst); //~ ERROR: Data race detected between Atomic Store on thread `` and Write on thread `` + }); + + j1.join().unwrap(); + j2.join().unwrap(); + } +} diff --git a/src/tools/miri/tests/fail/data_race/atomic_write_na_write_race1.stderr b/src/tools/miri/tests/fail/data_race/atomic_write_na_write_race1.stderr new file mode 100644 index 0000000000000..4c75f94d71cf5 --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/atomic_write_na_write_race1.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: Data race detected between Atomic Store on thread `` and Write on thread `` at ALLOC + --> $DIR/atomic_write_na_write_race1.rs:LL:CC + | +LL | (&*c.0).store(64, Ordering::SeqCst); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Data race detected between Atomic Store on thread `` and Write on thread `` at ALLOC + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside closure at $DIR/atomic_write_na_write_race1.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/data_race/atomic_write_na_write_race2.rs b/src/tools/miri/tests/fail/data_race/atomic_write_na_write_race2.rs new file mode 100644 index 0000000000000..658cddcc9c5b6 --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/atomic_write_na_write_race2.rs @@ -0,0 +1,32 @@ +// We want to control preemption here. +//@compile-flags: -Zmiri-preemption-rate=0 + +use std::sync::atomic::AtomicUsize; +use std::sync::atomic::Ordering; +use std::thread::spawn; + +#[derive(Copy, Clone)] +struct EvilSend(pub T); + +unsafe impl Send for EvilSend {} +unsafe impl Sync for EvilSend {} + +pub fn main() { + let mut a = AtomicUsize::new(0); + let b = &mut a as *mut AtomicUsize; + let c = EvilSend(b); + unsafe { + let j1 = spawn(move || { + let atomic_ref = &mut *c.0; + atomic_ref.store(64, Ordering::SeqCst); + }); + + let j2 = spawn(move || { + let atomic_ref = &mut *c.0; + *atomic_ref.get_mut() = 32; //~ ERROR: Data race detected between Write on thread `` and Atomic Store on thread `` + }); + + j1.join().unwrap(); + j2.join().unwrap(); + } +} diff --git a/src/tools/miri/tests/fail/data_race/atomic_write_na_write_race2.stderr b/src/tools/miri/tests/fail/data_race/atomic_write_na_write_race2.stderr new file mode 100644 index 0000000000000..8c7f14081c87b --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/atomic_write_na_write_race2.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: Data race detected between Write on thread `` and Atomic Store on thread `` at ALLOC + --> $DIR/atomic_write_na_write_race2.rs:LL:CC + | +LL | *atomic_ref.get_mut() = 32; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ Data race detected between Write on thread `` and Atomic Store on thread `` at ALLOC + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside closure at $DIR/atomic_write_na_write_race2.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/data_race/dangling_thread_async_race.rs b/src/tools/miri/tests/fail/data_race/dangling_thread_async_race.rs new file mode 100644 index 0000000000000..af2588e923240 --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/dangling_thread_async_race.rs @@ -0,0 +1,42 @@ +// We want to control preemption here. +//@compile-flags: -Zmiri-disable-isolation -Zmiri-preemption-rate=0 + +use std::mem; +use std::thread::{sleep, spawn}; +use std::time::Duration; + +#[derive(Copy, Clone)] +struct EvilSend(pub T); + +unsafe impl Send for EvilSend {} +unsafe impl Sync for EvilSend {} + +fn main() { + let mut a = 0u32; + let b = &mut a as *mut u32; + let c = EvilSend(b); + + let join = unsafe { + spawn(move || { + *c.0 = 32; + }) + }; + + // Detach the thread and sleep until it terminates + mem::drop(join); + sleep(Duration::from_millis(200)); + + // Spawn and immediately join a thread + // to execute the join code-path + // and ensure that data-race detection + // remains enabled nevertheless. + spawn(|| ()).join().unwrap(); + + let join2 = unsafe { + spawn(move || { + *c.0 = 64; //~ ERROR: Data race detected between Write on thread `` and Write on thread `` + }) + }; + + join2.join().unwrap(); +} diff --git a/src/tools/miri/tests/fail/data_race/dangling_thread_async_race.stderr b/src/tools/miri/tests/fail/data_race/dangling_thread_async_race.stderr new file mode 100644 index 0000000000000..663bb8d4af512 --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/dangling_thread_async_race.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: Data race detected between Write on thread `` and Write on thread `` at ALLOC + --> $DIR/dangling_thread_async_race.rs:LL:CC + | +LL | *c.0 = 64; + | ^^^^^^^^^ Data race detected between Write on thread `` and Write on thread `` at ALLOC + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside closure at $DIR/dangling_thread_async_race.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/data_race/dangling_thread_race.rs b/src/tools/miri/tests/fail/data_race/dangling_thread_race.rs new file mode 100644 index 0000000000000..1ee619c3f99d5 --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/dangling_thread_race.rs @@ -0,0 +1,38 @@ +// We want to control preemption here. +//@compile-flags: -Zmiri-disable-isolation -Zmiri-preemption-rate=0 + +use std::mem; +use std::thread::{sleep, spawn}; +use std::time::Duration; + +#[derive(Copy, Clone)] +struct EvilSend(pub T); + +unsafe impl Send for EvilSend {} +unsafe impl Sync for EvilSend {} + +fn main() { + let mut a = 0u32; + let b = &mut a as *mut u32; + let c = EvilSend(b); + + let join = unsafe { + spawn(move || { + *c.0 = 32; + }) + }; + + // Detach the thread and sleep until it terminates + mem::drop(join); + sleep(Duration::from_millis(200)); + + // Spawn and immediately join a thread + // to execute the join code-path + // and ensure that data-race detection + // remains enabled nevertheless. + spawn(|| ()).join().unwrap(); + + unsafe { + *c.0 = 64; //~ ERROR: Data race detected between Write on thread `main` and Write on thread `` + } +} diff --git a/src/tools/miri/tests/fail/data_race/dangling_thread_race.stderr b/src/tools/miri/tests/fail/data_race/dangling_thread_race.stderr new file mode 100644 index 0000000000000..ad3e1735378f3 --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/dangling_thread_race.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: Data race detected between Write on thread `main` and Write on thread `` at ALLOC + --> $DIR/dangling_thread_race.rs:LL:CC + | +LL | *c.0 = 64; + | ^^^^^^^^^ Data race detected between Write on thread `main` and Write on thread `` at ALLOC + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/dangling_thread_race.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/data_race/dealloc_read_race1.rs b/src/tools/miri/tests/fail/data_race/dealloc_read_race1.rs new file mode 100644 index 0000000000000..cbc02549a2541 --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/dealloc_read_race1.rs @@ -0,0 +1,38 @@ +// We want to control preemption here. +//@compile-flags: -Zmiri-preemption-rate=0 + +use std::thread::spawn; + +#[derive(Copy, Clone)] +struct EvilSend(pub T); + +unsafe impl Send for EvilSend {} +unsafe impl Sync for EvilSend {} + +extern "Rust" { + fn __rust_dealloc(ptr: *mut u8, size: usize, align: usize); +} + +pub fn main() { + // Shared atomic pointer + let pointer: *mut usize = Box::into_raw(Box::new(0usize)); + let ptr = EvilSend(pointer); + + unsafe { + let j1 = spawn(move || { + let _val = *ptr.0; + }); + + let j2 = spawn(move || { + __rust_dealloc( + //~^ ERROR: Data race detected between Deallocate on thread `` and Read on thread `` + ptr.0 as *mut _, + std::mem::size_of::(), + std::mem::align_of::(), + ); + }); + + j1.join().unwrap(); + j2.join().unwrap(); + } +} diff --git a/src/tools/miri/tests/fail/data_race/dealloc_read_race1.stderr b/src/tools/miri/tests/fail/data_race/dealloc_read_race1.stderr new file mode 100644 index 0000000000000..194c2260baaab --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/dealloc_read_race1.stderr @@ -0,0 +1,20 @@ +error: Undefined Behavior: Data race detected between Deallocate on thread `` and Read on thread `` at ALLOC + --> $DIR/dealloc_read_race1.rs:LL:CC + | +LL | / __rust_dealloc( +LL | | +LL | | ptr.0 as *mut _, +LL | | std::mem::size_of::(), +LL | | std::mem::align_of::(), +LL | | ); + | |_____________^ Data race detected between Deallocate on thread `` and Read on thread `` at ALLOC + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside closure at $DIR/dealloc_read_race1.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/data_race/dealloc_read_race2.rs b/src/tools/miri/tests/fail/data_race/dealloc_read_race2.rs new file mode 100644 index 0000000000000..24cce5d6fac1c --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/dealloc_read_race2.rs @@ -0,0 +1,39 @@ +// We want to control preemption here. +//@compile-flags: -Zmiri-preemption-rate=0 + +use std::thread::spawn; + +#[derive(Copy, Clone)] +struct EvilSend(pub T); + +unsafe impl Send for EvilSend {} +unsafe impl Sync for EvilSend {} + +extern "Rust" { + fn __rust_dealloc(ptr: *mut u8, size: usize, align: usize); +} + +pub fn main() { + // Shared atomic pointer + let pointer: *mut usize = Box::into_raw(Box::new(0usize)); + let ptr = EvilSend(pointer); + + unsafe { + let j1 = spawn(move || { + __rust_dealloc( + ptr.0 as *mut _, + std::mem::size_of::(), + std::mem::align_of::(), + ) + }); + + let j2 = spawn(move || { + // Also an error of the form: Data race detected between Read on thread `` and Deallocate on thread `` + // but the invalid allocation is detected first. + *ptr.0 //~ ERROR: dereferenced after this allocation got freed + }); + + j1.join().unwrap(); + j2.join().unwrap(); + } +} diff --git a/src/tools/miri/tests/fail/data_race/dealloc_read_race2.stderr b/src/tools/miri/tests/fail/data_race/dealloc_read_race2.stderr new file mode 100644 index 0000000000000..f303d57c8bd9c --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/dealloc_read_race2.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed + --> $DIR/dealloc_read_race2.rs:LL:CC + | +LL | *ptr.0 + | ^^^^^^ pointer to ALLOC was dereferenced after this allocation got freed + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside closure at $DIR/dealloc_read_race2.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/data_race/dealloc_read_race_stack.rs b/src/tools/miri/tests/fail/data_race/dealloc_read_race_stack.rs new file mode 100644 index 0000000000000..5484370f35c17 --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/dealloc_read_race_stack.rs @@ -0,0 +1,49 @@ +//@compile-flags: -Zmiri-disable-isolation -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0 + +use std::ptr::null_mut; +use std::sync::atomic::{AtomicPtr, Ordering}; +use std::thread::{sleep, spawn}; +use std::time::Duration; + +#[derive(Copy, Clone)] +struct EvilSend(pub T); + +unsafe impl Send for EvilSend {} +unsafe impl Sync for EvilSend {} + +pub fn main() { + // Shared atomic pointer + let pointer = AtomicPtr::new(null_mut::()); + let ptr = EvilSend(&pointer as *const AtomicPtr); + + // Note: this is scheduler-dependent + // the operations need to occur in + // order, otherwise the allocation is + // not visible to the other-thread to + // detect the race: + // 1. stack-allocate + // 2. read + // 3. stack-deallocate + unsafe { + let j1 = spawn(move || { + let pointer = &*ptr.0; + { + let mut stack_var = 0usize; + + pointer.store(&mut stack_var as *mut _, Ordering::Release); + + sleep(Duration::from_millis(200)); + + // Now `stack_var` gets deallocated. + } //~ ERROR: Data race detected between Deallocate on thread `` and Read on thread `` + }); + + let j2 = spawn(move || { + let pointer = &*ptr.0; + *pointer.load(Ordering::Acquire) + }); + + j1.join().unwrap(); + j2.join().unwrap(); + } +} diff --git a/src/tools/miri/tests/fail/data_race/dealloc_read_race_stack.stderr b/src/tools/miri/tests/fail/data_race/dealloc_read_race_stack.stderr new file mode 100644 index 0000000000000..c986e912f03ba --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/dealloc_read_race_stack.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: Data race detected between Deallocate on thread `` and Read on thread `` at ALLOC + --> $DIR/dealloc_read_race_stack.rs:LL:CC + | +LL | } + | ^ Data race detected between Deallocate on thread `` and Read on thread `` at ALLOC + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside closure at $DIR/dealloc_read_race_stack.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/data_race/dealloc_write_race1.rs b/src/tools/miri/tests/fail/data_race/dealloc_write_race1.rs new file mode 100644 index 0000000000000..23bf73fe8c5ad --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/dealloc_write_race1.rs @@ -0,0 +1,37 @@ +// We want to control preemption here. +//@compile-flags: -Zmiri-preemption-rate=0 + +use std::thread::spawn; + +#[derive(Copy, Clone)] +struct EvilSend(pub T); + +unsafe impl Send for EvilSend {} +unsafe impl Sync for EvilSend {} + +extern "Rust" { + fn __rust_dealloc(ptr: *mut u8, size: usize, align: usize); +} +pub fn main() { + // Shared atomic pointer + let pointer: *mut usize = Box::into_raw(Box::new(0usize)); + let ptr = EvilSend(pointer); + + unsafe { + let j1 = spawn(move || { + *ptr.0 = 2; + }); + + let j2 = spawn(move || { + __rust_dealloc( + //~^ ERROR: Data race detected between Deallocate on thread `` and Write on thread `` + ptr.0 as *mut _, + std::mem::size_of::(), + std::mem::align_of::(), + ); + }); + + j1.join().unwrap(); + j2.join().unwrap(); + } +} diff --git a/src/tools/miri/tests/fail/data_race/dealloc_write_race1.stderr b/src/tools/miri/tests/fail/data_race/dealloc_write_race1.stderr new file mode 100644 index 0000000000000..56eb0b519c484 --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/dealloc_write_race1.stderr @@ -0,0 +1,20 @@ +error: Undefined Behavior: Data race detected between Deallocate on thread `` and Write on thread `` at ALLOC + --> $DIR/dealloc_write_race1.rs:LL:CC + | +LL | / __rust_dealloc( +LL | | +LL | | ptr.0 as *mut _, +LL | | std::mem::size_of::(), +LL | | std::mem::align_of::(), +LL | | ); + | |_____________^ Data race detected between Deallocate on thread `` and Write on thread `` at ALLOC + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside closure at $DIR/dealloc_write_race1.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/data_race/dealloc_write_race2.rs b/src/tools/miri/tests/fail/data_race/dealloc_write_race2.rs new file mode 100644 index 0000000000000..7c8033e2335e9 --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/dealloc_write_race2.rs @@ -0,0 +1,38 @@ +// We want to control preemption here. +//@compile-flags: -Zmiri-preemption-rate=0 + +use std::thread::spawn; + +#[derive(Copy, Clone)] +struct EvilSend(pub T); + +unsafe impl Send for EvilSend {} +unsafe impl Sync for EvilSend {} + +extern "Rust" { + fn __rust_dealloc(ptr: *mut u8, size: usize, align: usize); +} +pub fn main() { + // Shared atomic pointer + let pointer: *mut usize = Box::into_raw(Box::new(0usize)); + let ptr = EvilSend(pointer); + + unsafe { + let j1 = spawn(move || { + __rust_dealloc( + ptr.0 as *mut _, + std::mem::size_of::(), + std::mem::align_of::(), + ); + }); + + let j2 = spawn(move || { + // Also an error of the form: Data race detected between Write on thread `` and Deallocate on thread `` + // but the invalid allocation is detected first. + *ptr.0 = 2; //~ ERROR: dereferenced after this allocation got freed + }); + + j1.join().unwrap(); + j2.join().unwrap(); + } +} diff --git a/src/tools/miri/tests/fail/data_race/dealloc_write_race2.stderr b/src/tools/miri/tests/fail/data_race/dealloc_write_race2.stderr new file mode 100644 index 0000000000000..23b8e9ade0e0e --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/dealloc_write_race2.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed + --> $DIR/dealloc_write_race2.rs:LL:CC + | +LL | *ptr.0 = 2; + | ^^^^^^^^^^ pointer to ALLOC was dereferenced after this allocation got freed + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside closure at $DIR/dealloc_write_race2.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/data_race/dealloc_write_race_stack.rs b/src/tools/miri/tests/fail/data_race/dealloc_write_race_stack.rs new file mode 100644 index 0000000000000..1872abfe021b3 --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/dealloc_write_race_stack.rs @@ -0,0 +1,49 @@ +//@compile-flags: -Zmiri-disable-isolation -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0 + +use std::ptr::null_mut; +use std::sync::atomic::{AtomicPtr, Ordering}; +use std::thread::{sleep, spawn}; +use std::time::Duration; + +#[derive(Copy, Clone)] +struct EvilSend(pub T); + +unsafe impl Send for EvilSend {} +unsafe impl Sync for EvilSend {} + +pub fn main() { + // Shared atomic pointer + let pointer = AtomicPtr::new(null_mut::()); + let ptr = EvilSend(&pointer as *const AtomicPtr); + + // Note: this is scheduler-dependent + // the operations need to occur in + // order, otherwise the allocation is + // not visible to the other-thread to + // detect the race: + // 1. stack-allocate + // 2. read + // 3. stack-deallocate + unsafe { + let j1 = spawn(move || { + let pointer = &*ptr.0; + { + let mut stack_var = 0usize; + + pointer.store(&mut stack_var as *mut _, Ordering::Release); + + sleep(Duration::from_millis(200)); + + // Now `stack_var` gets deallocated. + } //~ ERROR: Data race detected between Deallocate on thread `` and Write on thread `` + }); + + let j2 = spawn(move || { + let pointer = &*ptr.0; + *pointer.load(Ordering::Acquire) = 3; + }); + + j1.join().unwrap(); + j2.join().unwrap(); + } +} diff --git a/src/tools/miri/tests/fail/data_race/dealloc_write_race_stack.stderr b/src/tools/miri/tests/fail/data_race/dealloc_write_race_stack.stderr new file mode 100644 index 0000000000000..7b77e2470a1ab --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/dealloc_write_race_stack.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: Data race detected between Deallocate on thread `` and Write on thread `` at ALLOC + --> $DIR/dealloc_write_race_stack.rs:LL:CC + | +LL | } + | ^ Data race detected between Deallocate on thread `` and Write on thread `` at ALLOC + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside closure at $DIR/dealloc_write_race_stack.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/data_race/enable_after_join_to_main.rs b/src/tools/miri/tests/fail/data_race/enable_after_join_to_main.rs new file mode 100644 index 0000000000000..c11239da7febb --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/enable_after_join_to_main.rs @@ -0,0 +1,39 @@ +// We want to control preemption here. +//@compile-flags: -Zmiri-preemption-rate=0 + +use std::thread::spawn; + +#[derive(Copy, Clone)] +struct EvilSend(pub T); + +unsafe impl Send for EvilSend {} +unsafe impl Sync for EvilSend {} + +pub fn main() { + // Enable and then join with multiple threads. + let t1 = spawn(|| ()); + let t2 = spawn(|| ()); + let t3 = spawn(|| ()); + let t4 = spawn(|| ()); + t1.join().unwrap(); + t2.join().unwrap(); + t3.join().unwrap(); + t4.join().unwrap(); + + // Perform write-write data race detection. + let mut a = 0u32; + let b = &mut a as *mut u32; + let c = EvilSend(b); + unsafe { + let j1 = spawn(move || { + *c.0 = 32; + }); + + let j2 = spawn(move || { + *c.0 = 64; //~ ERROR: Data race detected between Write on thread `` and Write on thread `` + }); + + j1.join().unwrap(); + j2.join().unwrap(); + } +} diff --git a/src/tools/miri/tests/fail/data_race/enable_after_join_to_main.stderr b/src/tools/miri/tests/fail/data_race/enable_after_join_to_main.stderr new file mode 100644 index 0000000000000..26c07ae6962b5 --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/enable_after_join_to_main.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: Data race detected between Write on thread `` and Write on thread `` at ALLOC + --> $DIR/enable_after_join_to_main.rs:LL:CC + | +LL | *c.0 = 64; + | ^^^^^^^^^ Data race detected between Write on thread `` and Write on thread `` at ALLOC + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside closure at $DIR/enable_after_join_to_main.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/data_race/fence_after_load.rs b/src/tools/miri/tests/fail/data_race/fence_after_load.rs new file mode 100644 index 0000000000000..ae443908598fe --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/fence_after_load.rs @@ -0,0 +1,24 @@ +// We want to control preemption here. +//@compile-flags: -Zmiri-disable-isolation -Zmiri-preemption-rate=0 +use std::sync::atomic::{fence, AtomicUsize, Ordering}; +use std::sync::Arc; +use std::thread; +use std::time::Duration; + +fn main() { + static mut V: u32 = 0; + let a = Arc::new(AtomicUsize::default()); + let b = a.clone(); + thread::spawn(move || { + unsafe { V = 1 } + b.store(1, Ordering::SeqCst); + }); + thread::sleep(Duration::from_millis(100)); + fence(Ordering::SeqCst); + // Imagine the other thread's actions happening here. + assert_eq!(a.load(Ordering::Relaxed), 1); + // The fence is useless, since it did not happen-after the `store` in the other thread. + // Hence this is a data race. + // Also see https://github.com/rust-lang/miri/issues/2192. + unsafe { V = 2 } //~ERROR: Data race detected between Write on thread `main` and Write on thread `` +} diff --git a/src/tools/miri/tests/fail/data_race/fence_after_load.stderr b/src/tools/miri/tests/fail/data_race/fence_after_load.stderr new file mode 100644 index 0000000000000..0abfe213db17d --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/fence_after_load.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: Data race detected between Write on thread `main` and Write on thread `` at ALLOC + --> $DIR/fence_after_load.rs:LL:CC + | +LL | unsafe { V = 2 } + | ^^^^^ Data race detected between Write on thread `main` and Write on thread `` at ALLOC + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/fence_after_load.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/data_race/read_write_race.rs b/src/tools/miri/tests/fail/data_race/read_write_race.rs new file mode 100644 index 0000000000000..482dd2df7df91 --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/read_write_race.rs @@ -0,0 +1,28 @@ +// We want to control preemption here. +//@compile-flags: -Zmiri-preemption-rate=0 + +use std::thread::spawn; + +#[derive(Copy, Clone)] +struct EvilSend(pub T); + +unsafe impl Send for EvilSend {} +unsafe impl Sync for EvilSend {} + +pub fn main() { + let mut a = 0u32; + let b = &mut a as *mut u32; + let c = EvilSend(b); + unsafe { + let j1 = spawn(move || { + let _val = *c.0; + }); + + let j2 = spawn(move || { + *c.0 = 64; //~ ERROR: Data race detected between Write on thread `` and Read on thread `` + }); + + j1.join().unwrap(); + j2.join().unwrap(); + } +} diff --git a/src/tools/miri/tests/fail/data_race/read_write_race.stderr b/src/tools/miri/tests/fail/data_race/read_write_race.stderr new file mode 100644 index 0000000000000..08a19537312cf --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/read_write_race.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: Data race detected between Write on thread `` and Read on thread `` at ALLOC + --> $DIR/read_write_race.rs:LL:CC + | +LL | *c.0 = 64; + | ^^^^^^^^^ Data race detected between Write on thread `` and Read on thread `` at ALLOC + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside closure at $DIR/read_write_race.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/data_race/read_write_race_stack.rs b/src/tools/miri/tests/fail/data_race/read_write_race_stack.rs new file mode 100644 index 0000000000000..1b4932439b010 --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/read_write_race_stack.rs @@ -0,0 +1,56 @@ +//@compile-flags: -Zmiri-disable-isolation -Zmir-opt-level=0 -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0 + +// Note: mir-opt-level set to 0 to prevent the read of stack_var in thread 1 +// from being optimized away and preventing the detection of the data-race. + +use std::ptr::null_mut; +use std::sync::atomic::{AtomicPtr, Ordering}; +use std::thread::{sleep, spawn}; +use std::time::Duration; + +#[derive(Copy, Clone)] +struct EvilSend(pub T); + +unsafe impl Send for EvilSend {} +unsafe impl Sync for EvilSend {} + +pub fn main() { + // Shared atomic pointer + let pointer = AtomicPtr::new(null_mut::()); + let ptr = EvilSend(&pointer as *const AtomicPtr); + + // Note: this is scheduler-dependent + // the operations need to occur in + // order, otherwise the allocation is + // not visible to the other-thread to + // detect the race: + // 1. stack-allocate + // 2. atomic_store + // 3. atomic_load + // 4. write-value + // 5. read-value + unsafe { + let j1 = spawn(move || { + // Concurrent allocate the memory. + // Uses relaxed semantics to not generate + // a release sequence. + let pointer = &*ptr.0; + + let mut stack_var = 0usize; + + pointer.store(&mut stack_var as *mut _, Ordering::Release); + + sleep(Duration::from_millis(200)); + + stack_var //~ ERROR: Data race detected between Read on thread `` and Write on thread `` + }); + + let j2 = spawn(move || { + let pointer = &*ptr.0; + *pointer.load(Ordering::Acquire) = 3; + }); + + j1.join().unwrap(); + j2.join().unwrap(); + } +} diff --git a/src/tools/miri/tests/fail/data_race/read_write_race_stack.stderr b/src/tools/miri/tests/fail/data_race/read_write_race_stack.stderr new file mode 100644 index 0000000000000..20f137afe7329 --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/read_write_race_stack.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: Data race detected between Read on thread `` and Write on thread `` at ALLOC + --> $DIR/read_write_race_stack.rs:LL:CC + | +LL | stack_var + | ^^^^^^^^^ Data race detected between Read on thread `` and Write on thread `` at ALLOC + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside closure at $DIR/read_write_race_stack.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/data_race/relax_acquire_race.rs b/src/tools/miri/tests/fail/data_race/relax_acquire_race.rs new file mode 100644 index 0000000000000..240b4c90eb225 --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/relax_acquire_race.rs @@ -0,0 +1,50 @@ +//@compile-flags: -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0 + +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::thread::spawn; + +#[derive(Copy, Clone)] +struct EvilSend(pub T); + +unsafe impl Send for EvilSend {} +unsafe impl Sync for EvilSend {} + +static SYNC: AtomicUsize = AtomicUsize::new(0); + +pub fn main() { + let mut a = 0u32; + let b = &mut a as *mut u32; + let c = EvilSend(b); + + // Note: this is scheduler-dependent + // the operations need to occur in + // order: + // 1. store release : 1 + // 2. load acquire : 1 + // 3. store relaxed : 2 + // 4. load acquire : 2 + unsafe { + let j1 = spawn(move || { + *c.0 = 1; + SYNC.store(1, Ordering::Release); + }); + + let j2 = spawn(move || { + if SYNC.load(Ordering::Acquire) == 1 { + SYNC.store(2, Ordering::Relaxed); + } + }); + + let j3 = spawn(move || { + if SYNC.load(Ordering::Acquire) == 2 { + *c.0 //~ ERROR: Data race detected between Read on thread `` and Write on thread `` + } else { + 0 + } + }); + + j1.join().unwrap(); + j2.join().unwrap(); + j3.join().unwrap(); + } +} diff --git a/src/tools/miri/tests/fail/data_race/relax_acquire_race.stderr b/src/tools/miri/tests/fail/data_race/relax_acquire_race.stderr new file mode 100644 index 0000000000000..6121c25db22d7 --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/relax_acquire_race.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: Data race detected between Read on thread `` and Write on thread `` at ALLOC + --> $DIR/relax_acquire_race.rs:LL:CC + | +LL | *c.0 + | ^^^^ Data race detected between Read on thread `` and Write on thread `` at ALLOC + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside closure at $DIR/relax_acquire_race.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/data_race/release_seq_race.rs b/src/tools/miri/tests/fail/data_race/release_seq_race.rs new file mode 100644 index 0000000000000..5ae801278357b --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/release_seq_race.rs @@ -0,0 +1,54 @@ +//@compile-flags: -Zmiri-disable-isolation -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0 + +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::thread::{sleep, spawn}; +use std::time::Duration; + +#[derive(Copy, Clone)] +struct EvilSend(pub T); + +unsafe impl Send for EvilSend {} +unsafe impl Sync for EvilSend {} + +static SYNC: AtomicUsize = AtomicUsize::new(0); + +pub fn main() { + let mut a = 0u32; + let b = &mut a as *mut u32; + let c = EvilSend(b); + + // Note: this is scheduler-dependent + // the operations need to occur in + // order, the sleep operations currently + // force the desired ordering: + // 1. store release : 1 + // 2. store relaxed : 2 + // 3. store relaxed : 3 + // 4. load acquire : 3 + unsafe { + let j1 = spawn(move || { + *c.0 = 1; + SYNC.store(1, Ordering::Release); + sleep(Duration::from_millis(200)); + SYNC.store(3, Ordering::Relaxed); + }); + + let j2 = spawn(move || { + // Blocks the acquire-release sequence + SYNC.store(2, Ordering::Relaxed); + }); + + let j3 = spawn(move || { + sleep(Duration::from_millis(500)); + if SYNC.load(Ordering::Acquire) == 3 { + *c.0 //~ ERROR: Data race detected between Read on thread `` and Write on thread `` + } else { + 0 + } + }); + + j1.join().unwrap(); + j2.join().unwrap(); + j3.join().unwrap(); + } +} diff --git a/src/tools/miri/tests/fail/data_race/release_seq_race.stderr b/src/tools/miri/tests/fail/data_race/release_seq_race.stderr new file mode 100644 index 0000000000000..777bc4adadc6d --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/release_seq_race.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: Data race detected between Read on thread `` and Write on thread `` at ALLOC + --> $DIR/release_seq_race.rs:LL:CC + | +LL | *c.0 + | ^^^^ Data race detected between Read on thread `` and Write on thread `` at ALLOC + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside closure at $DIR/release_seq_race.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/data_race/release_seq_race_same_thread.rs b/src/tools/miri/tests/fail/data_race/release_seq_race_same_thread.rs new file mode 100644 index 0000000000000..63e6dc2dd71b9 --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/release_seq_race_same_thread.rs @@ -0,0 +1,49 @@ +//@compile-flags: -Zmiri-disable-isolation -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0 + +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::thread::spawn; + +#[derive(Copy, Clone)] +struct EvilSend(pub T); + +unsafe impl Send for EvilSend {} +unsafe impl Sync for EvilSend {} + +static SYNC: AtomicUsize = AtomicUsize::new(0); + +pub fn main() { + let mut a = 0u32; + let b = &mut a as *mut u32; + let c = EvilSend(b); + + // Note: this is scheduler-dependent + // the operations need to occur in + // order, the sleep operations currently + // force the desired ordering: + // 1. store release : 1 + // 2. store relaxed : 2 + // 3. load acquire : 2 + unsafe { + let j1 = spawn(move || { + *c.0 = 1; + SYNC.store(1, Ordering::Release); + + // C++20 update to release sequences + // makes this block the release sequence + // despite the being on the same thread + // as the release store. + SYNC.store(2, Ordering::Relaxed); + }); + + let j2 = spawn(move || { + if SYNC.load(Ordering::Acquire) == 2 { + *c.0 //~ ERROR: Data race detected between Read on thread `` and Write on thread `` + } else { + 0 + } + }); + + j1.join().unwrap(); + j2.join().unwrap(); + } +} diff --git a/src/tools/miri/tests/fail/data_race/release_seq_race_same_thread.stderr b/src/tools/miri/tests/fail/data_race/release_seq_race_same_thread.stderr new file mode 100644 index 0000000000000..0fcb192d920fd --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/release_seq_race_same_thread.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: Data race detected between Read on thread `` and Write on thread `` at ALLOC + --> $DIR/release_seq_race_same_thread.rs:LL:CC + | +LL | *c.0 + | ^^^^ Data race detected between Read on thread `` and Write on thread `` at ALLOC + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside closure at $DIR/release_seq_race_same_thread.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/data_race/rmw_race.rs b/src/tools/miri/tests/fail/data_race/rmw_race.rs new file mode 100644 index 0000000000000..122780d11aa1f --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/rmw_race.rs @@ -0,0 +1,51 @@ +//@compile-flags: -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0 + +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::thread::spawn; + +#[derive(Copy, Clone)] +struct EvilSend(pub T); + +unsafe impl Send for EvilSend {} +unsafe impl Sync for EvilSend {} + +static SYNC: AtomicUsize = AtomicUsize::new(0); + +pub fn main() { + let mut a = 0u32; + let b = &mut a as *mut u32; + let c = EvilSend(b); + + // Note: this is scheduler-dependent + // the operations need to occur in + // order: + // 1. store release : 1 + // 2. RMW relaxed : 1 -> 2 + // 3. store relaxed : 3 + // 4. load acquire : 3 + unsafe { + let j1 = spawn(move || { + *c.0 = 1; + SYNC.store(1, Ordering::Release); + }); + + let j2 = spawn(move || { + if SYNC.swap(2, Ordering::Relaxed) == 1 { + // Blocks the acquire-release sequence + SYNC.store(3, Ordering::Relaxed); + } + }); + + let j3 = spawn(move || { + if SYNC.load(Ordering::Acquire) == 3 { + *c.0 //~ ERROR: Data race detected between Read on thread `` and Write on thread `` + } else { + 0 + } + }); + + j1.join().unwrap(); + j2.join().unwrap(); + j3.join().unwrap(); + } +} diff --git a/src/tools/miri/tests/fail/data_race/rmw_race.stderr b/src/tools/miri/tests/fail/data_race/rmw_race.stderr new file mode 100644 index 0000000000000..3ae6f3b84fe12 --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/rmw_race.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: Data race detected between Read on thread `` and Write on thread `` at ALLOC + --> $DIR/rmw_race.rs:LL:CC + | +LL | *c.0 + | ^^^^ Data race detected between Read on thread `` and Write on thread `` at ALLOC + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside closure at $DIR/rmw_race.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/data_race/stack_pop_race.rs b/src/tools/miri/tests/fail/data_race/stack_pop_race.rs new file mode 100644 index 0000000000000..8f371a680f11d --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/stack_pop_race.rs @@ -0,0 +1,25 @@ +//@ignore-target-windows: Concurrency on Windows is not supported yet. +//@compile-flags: -Zmiri-preemption-rate=0 +use std::thread; + +#[derive(Copy, Clone)] +struct MakeSend(*const i32); +unsafe impl Send for MakeSend {} + +fn main() { + race(0); +} + +// Using an argument for the ptr to point to, since those do not get StorageDead. +fn race(local: i32) { + let ptr = MakeSend(&local as *const i32); + thread::spawn(move || { + let ptr = ptr; + let _val = unsafe { *ptr.0 }; + }); + // Make the other thread go first so that it does not UAF. + thread::yield_now(); + // Deallocating the local (when `main` returns) + // races with the read in the other thread. + // Make sure the error points at this function's end, not just the call site. +} //~ERROR: Data race detected between Deallocate on thread `main` and Read on thread `` diff --git a/src/tools/miri/tests/fail/data_race/stack_pop_race.stderr b/src/tools/miri/tests/fail/data_race/stack_pop_race.stderr new file mode 100644 index 0000000000000..5de27108ab633 --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/stack_pop_race.stderr @@ -0,0 +1,20 @@ +error: Undefined Behavior: Data race detected between Deallocate on thread `main` and Read on thread `` at ALLOC + --> $DIR/stack_pop_race.rs:LL:CC + | +LL | } + | ^ Data race detected between Deallocate on thread `main` and Read on thread `` at ALLOC + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `race` at $DIR/stack_pop_race.rs:LL:CC +note: inside `main` at $DIR/stack_pop_race.rs:LL:CC + --> $DIR/stack_pop_race.rs:LL:CC + | +LL | race(0); + | ^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/data_race/write_write_race.rs b/src/tools/miri/tests/fail/data_race/write_write_race.rs new file mode 100644 index 0000000000000..13c31c87cbbae --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/write_write_race.rs @@ -0,0 +1,28 @@ +// We want to control preemption here. +//@compile-flags: -Zmiri-preemption-rate=0 + +use std::thread::spawn; + +#[derive(Copy, Clone)] +struct EvilSend(pub T); + +unsafe impl Send for EvilSend {} +unsafe impl Sync for EvilSend {} + +pub fn main() { + let mut a = 0u32; + let b = &mut a as *mut u32; + let c = EvilSend(b); + unsafe { + let j1 = spawn(move || { + *c.0 = 32; + }); + + let j2 = spawn(move || { + *c.0 = 64; //~ ERROR: Data race detected between Write on thread `` and Write on thread `` + }); + + j1.join().unwrap(); + j2.join().unwrap(); + } +} diff --git a/src/tools/miri/tests/fail/data_race/write_write_race.stderr b/src/tools/miri/tests/fail/data_race/write_write_race.stderr new file mode 100644 index 0000000000000..ee7072ccf5d17 --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/write_write_race.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: Data race detected between Write on thread `` and Write on thread `` at ALLOC + --> $DIR/write_write_race.rs:LL:CC + | +LL | *c.0 = 64; + | ^^^^^^^^^ Data race detected between Write on thread `` and Write on thread `` at ALLOC + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside closure at $DIR/write_write_race.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/data_race/write_write_race_stack.rs b/src/tools/miri/tests/fail/data_race/write_write_race_stack.rs new file mode 100644 index 0000000000000..731ac8b26aa74 --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/write_write_race_stack.rs @@ -0,0 +1,56 @@ +//@compile-flags: -Zmiri-disable-isolation -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0 + +use std::ptr::null_mut; +use std::sync::atomic::{AtomicPtr, Ordering}; +use std::thread::{sleep, spawn}; +use std::time::Duration; + +#[derive(Copy, Clone)] +struct EvilSend(pub T); + +unsafe impl Send for EvilSend {} +unsafe impl Sync for EvilSend {} + +pub fn main() { + // Shared atomic pointer + let pointer = AtomicPtr::new(null_mut::()); + let ptr = EvilSend(&pointer as *const AtomicPtr); + + // Note: this is scheduler-dependent + // the operations need to occur in + // order, otherwise the allocation is + // not visible to the other-thread to + // detect the race: + // 1. stack-allocate + // 2. atomic_store + // 3. atomic_load + // 4. write-value + // 5. write-value + unsafe { + let j1 = spawn(move || { + // Concurrent allocate the memory. + // Uses relaxed semantics to not generate + // a release sequence. + let pointer = &*ptr.0; + + let mut stack_var = 0usize; + + pointer.store(&mut stack_var as *mut _, Ordering::Release); + + sleep(Duration::from_millis(200)); + + stack_var = 1usize; //~ ERROR: Data race detected between Write on thread `` and Write on thread `` + + // read to silence errors + stack_var + }); + + let j2 = spawn(move || { + let pointer = &*ptr.0; + *pointer.load(Ordering::Acquire) = 3; + }); + + j1.join().unwrap(); + j2.join().unwrap(); + } +} diff --git a/src/tools/miri/tests/fail/data_race/write_write_race_stack.stderr b/src/tools/miri/tests/fail/data_race/write_write_race_stack.stderr new file mode 100644 index 0000000000000..ceb473c2a4a41 --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/write_write_race_stack.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: Data race detected between Write on thread `` and Write on thread `` at ALLOC + --> $DIR/write_write_race_stack.rs:LL:CC + | +LL | stack_var = 1usize; + | ^^^^^^^^^^^^^^^^^^ Data race detected between Write on thread `` and Write on thread `` at ALLOC + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside closure at $DIR/write_write_race_stack.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/dyn-call-trait-mismatch.rs b/src/tools/miri/tests/fail/dyn-call-trait-mismatch.rs new file mode 100644 index 0000000000000..0e7c3dbcc040f --- /dev/null +++ b/src/tools/miri/tests/fail/dyn-call-trait-mismatch.rs @@ -0,0 +1,16 @@ +trait T1 { + fn method1(self: Box); +} +trait T2 { + fn method2(self: Box); +} + +impl T1 for i32 { + fn method1(self: Box) {} +} + +fn main() { + let r = Box::new(0) as Box; + let r2: Box = unsafe { std::mem::transmute(r) }; + r2.method2(); //~ERROR: call on a pointer whose vtable does not match its type +} diff --git a/src/tools/miri/tests/fail/dyn-call-trait-mismatch.stderr b/src/tools/miri/tests/fail/dyn-call-trait-mismatch.stderr new file mode 100644 index 0000000000000..03272105c4146 --- /dev/null +++ b/src/tools/miri/tests/fail/dyn-call-trait-mismatch.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: `dyn` call on a pointer whose vtable does not match its type + --> $DIR/dyn-call-trait-mismatch.rs:LL:CC + | +LL | r2.method2(); + | ^^^^^^^^^^^^ `dyn` call on a pointer whose vtable does not match its type + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/dyn-call-trait-mismatch.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/dyn-upcast-trait-mismatch.rs b/src/tools/miri/tests/fail/dyn-upcast-trait-mismatch.rs new file mode 100644 index 0000000000000..648ac07c43ec9 --- /dev/null +++ b/src/tools/miri/tests/fail/dyn-upcast-trait-mismatch.rs @@ -0,0 +1,57 @@ +#![feature(trait_upcasting)] +#![allow(incomplete_features)] + +trait Foo: PartialEq + std::fmt::Debug + Send + Sync { + fn a(&self) -> i32 { + 10 + } + + fn z(&self) -> i32 { + 11 + } + + fn y(&self) -> i32 { + 12 + } +} + +trait Bar: Foo { + fn b(&self) -> i32 { + 20 + } + + fn w(&self) -> i32 { + 21 + } +} + +trait Baz: Bar { + fn c(&self) -> i32 { + 30 + } +} + +impl Foo for i32 { + fn a(&self) -> i32 { + 100 + } +} + +impl Bar for i32 { + fn b(&self) -> i32 { + 200 + } +} + +impl Baz for i32 { + fn c(&self) -> i32 { + 300 + } +} + +fn main() { + let baz: &dyn Baz = &1; + let baz_fake: &dyn Bar = unsafe { std::mem::transmute(baz) }; + let _err = baz_fake as &dyn Foo; + //~^ERROR: upcast on a pointer whose vtable does not match its type +} diff --git a/src/tools/miri/tests/fail/dyn-upcast-trait-mismatch.stderr b/src/tools/miri/tests/fail/dyn-upcast-trait-mismatch.stderr new file mode 100644 index 0000000000000..21870ef3733e8 --- /dev/null +++ b/src/tools/miri/tests/fail/dyn-upcast-trait-mismatch.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: upcast on a pointer whose vtable does not match its type + --> $DIR/dyn-upcast-trait-mismatch.rs:LL:CC + | +LL | let _err = baz_fake as &dyn Foo; + | ^^^^^^^^ upcast on a pointer whose vtable does not match its type + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/dyn-upcast-trait-mismatch.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/environ-gets-deallocated.rs b/src/tools/miri/tests/fail/environ-gets-deallocated.rs new file mode 100644 index 0000000000000..e4597140b8440 --- /dev/null +++ b/src/tools/miri/tests/fail/environ-gets-deallocated.rs @@ -0,0 +1,24 @@ +//@ignore-target-windows: Windows does not have a global environ list that the program can access directly + +#[cfg(any(target_os = "linux", target_os = "freebsd"))] +fn get_environ() -> *const *const u8 { + extern "C" { + static mut environ: *const *const u8; + } + unsafe { environ } +} + +#[cfg(target_os = "macos")] +fn get_environ() -> *const *const u8 { + extern "C" { + fn _NSGetEnviron() -> *mut *const *const u8; + } + unsafe { *_NSGetEnviron() } +} + +fn main() { + let pointer = get_environ(); + let _x = unsafe { *pointer }; + std::env::set_var("FOO", "BAR"); + let _y = unsafe { *pointer }; //~ ERROR: dereferenced after this allocation got freed +} diff --git a/src/tools/miri/tests/fail/environ-gets-deallocated.stderr b/src/tools/miri/tests/fail/environ-gets-deallocated.stderr new file mode 100644 index 0000000000000..a2d343bf8651c --- /dev/null +++ b/src/tools/miri/tests/fail/environ-gets-deallocated.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed + --> $DIR/environ-gets-deallocated.rs:LL:CC + | +LL | let _y = unsafe { *pointer }; + | ^^^^^^^^ pointer to ALLOC was dereferenced after this allocation got freed + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/environ-gets-deallocated.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/erroneous_const.rs b/src/tools/miri/tests/fail/erroneous_const.rs new file mode 100644 index 0000000000000..c35a905035932 --- /dev/null +++ b/src/tools/miri/tests/fail/erroneous_const.rs @@ -0,0 +1,20 @@ +//! Make sure we detect erroneous constants post-monomorphization even when they are unused. +//! (https://github.com/rust-lang/miri/issues/1382) +// Inlining changes the error location +//@compile-flags: -Zmir-opt-level=0 +#![feature(never_type)] +#![warn(warnings, const_err)] + +struct PrintName(T); +impl PrintName { + const VOID: ! = panic!(); //~ERROR: evaluation of `PrintName::::VOID` failed +} + +fn no_codegen() { + if false { + let _ = PrintName::::VOID; //~ERROR: post-monomorphization error + } +} +fn main() { + no_codegen::(); +} diff --git a/src/tools/miri/tests/fail/erroneous_const.stderr b/src/tools/miri/tests/fail/erroneous_const.stderr new file mode 100644 index 0000000000000..8138d69f4031e --- /dev/null +++ b/src/tools/miri/tests/fail/erroneous_const.stderr @@ -0,0 +1,26 @@ +error[E0080]: evaluation of `PrintName::::VOID` failed + --> $DIR/erroneous_const.rs:LL:CC + | +LL | const VOID: ! = panic!(); + | ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/erroneous_const.rs:LL:CC + | + = note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: post-monomorphization error: referenced constant has errors + --> $DIR/erroneous_const.rs:LL:CC + | +LL | let _ = PrintName::::VOID; + | ^^^^^^^^^^^^^^^^^^^^ referenced constant has errors + | + = note: inside `no_codegen::` at $DIR/erroneous_const.rs:LL:CC +note: inside `main` at $DIR/erroneous_const.rs:LL:CC + --> $DIR/erroneous_const.rs:LL:CC + | +LL | no_codegen::(); + | ^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0080`. diff --git a/src/tools/miri/tests/fail/erroneous_const2.rs b/src/tools/miri/tests/fail/erroneous_const2.rs new file mode 100644 index 0000000000000..6628166cfacdf --- /dev/null +++ b/src/tools/miri/tests/fail/erroneous_const2.rs @@ -0,0 +1,13 @@ +const X: u32 = 5; +const Y: u32 = 6; +const FOO: u32 = [X - Y, Y - X][(X < Y) as usize]; +//~^ERROR: any use of this value +//~|WARN: previously accepted + +#[rustfmt::skip] // rustfmt bug: https://github.com/rust-lang/rustfmt/issues/5391 +fn main() { + println!("{}", FOO); //~ERROR: post-monomorphization error + //~|ERROR: evaluation of constant value failed + //~|ERROR: erroneous constant used + //~|WARN: previously accepted +} diff --git a/src/tools/miri/tests/fail/erroneous_const2.stderr b/src/tools/miri/tests/fail/erroneous_const2.stderr new file mode 100644 index 0000000000000..05ed8ea1c14bc --- /dev/null +++ b/src/tools/miri/tests/fail/erroneous_const2.stderr @@ -0,0 +1,39 @@ +error: any use of this value will cause an error + --> $DIR/erroneous_const2.rs:LL:CC + | +LL | const FOO: u32 = [X - Y, Y - X][(X < Y) as usize]; + | -------------- ^^^^^ attempt to compute `5_u32 - 6_u32`, which would overflow + | + = note: `#[deny(const_err)]` on by default + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + +error[E0080]: evaluation of constant value failed + --> $DIR/erroneous_const2.rs:LL:CC + | +LL | println!("{}", FOO); + | ^^^ referenced constant has errors + +error: erroneous constant used + --> $DIR/erroneous_const2.rs:LL:CC + | +LL | println!("{}", FOO); + | ^^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: post-monomorphization error: referenced constant has errors + --> $DIR/erroneous_const2.rs:LL:CC + | +LL | println!("{}", FOO); + | ^^^ referenced constant has errors + | + = note: inside `main` at $DIR/erroneous_const2.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 4 previous errors + +For more information about this error, try `rustc --explain E0080`. diff --git a/src/tools/miri/tests/fail/extern_static.rs b/src/tools/miri/tests/fail/extern_static.rs new file mode 100644 index 0000000000000..f8805db8d1439 --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static.rs @@ -0,0 +1,9 @@ +//! Even referencing an unknown `extern static` already triggers an error. + +extern "C" { + static mut FOO: i32; +} + +fn main() { + let _val = unsafe { std::ptr::addr_of!(FOO) }; //~ ERROR: is not supported by Miri +} diff --git a/src/tools/miri/tests/fail/extern_static.stderr b/src/tools/miri/tests/fail/extern_static.stderr new file mode 100644 index 0000000000000..fa0d55e5f6781 --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static.stderr @@ -0,0 +1,14 @@ +error: unsupported operation: `extern` static `FOO` from crate `extern_static` is not supported by Miri + --> $DIR/extern_static.rs:LL:CC + | +LL | let _val = unsafe { std::ptr::addr_of!(FOO) }; + | ^^^ `extern` static `FOO` from crate `extern_static` is not supported by Miri + | + = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support + = note: BACKTRACE: + = note: inside `main` at $DIR/extern_static.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/extern_static_in_const.rs b/src/tools/miri/tests/fail/extern_static_in_const.rs new file mode 100644 index 0000000000000..31c192bf44bea --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static_in_const.rs @@ -0,0 +1,11 @@ +//! Even referencing an unknown `extern static` already triggers an error. + +extern "C" { + static E: [u8; 0]; +} + +static X: &'static [u8; 0] = unsafe { &E }; + +fn main() { + let _val = X; //~ ERROR: is not supported by Miri +} diff --git a/src/tools/miri/tests/fail/extern_static_in_const.stderr b/src/tools/miri/tests/fail/extern_static_in_const.stderr new file mode 100644 index 0000000000000..e4ee8f1acba29 --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static_in_const.stderr @@ -0,0 +1,14 @@ +error: unsupported operation: `extern` static `E` from crate `extern_static_in_const` is not supported by Miri + --> $DIR/extern_static_in_const.rs:LL:CC + | +LL | let _val = X; + | ^ `extern` static `E` from crate `extern_static_in_const` is not supported by Miri + | + = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support + = note: BACKTRACE: + = note: inside `main` at $DIR/extern_static_in_const.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/extern_static_wrong_size.rs b/src/tools/miri/tests/fail/extern_static_wrong_size.rs new file mode 100644 index 0000000000000..17061f0e5c81c --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static_wrong_size.rs @@ -0,0 +1,10 @@ +//@ only-target-linux: we need a specific extern supported on this target +//@normalize-stderr-test: "[48] bytes" -> "N bytes" + +extern "C" { + static mut environ: i8; +} + +fn main() { + let _val = unsafe { environ }; //~ ERROR: /has been declared with a size of 1 bytes and alignment of 1 bytes, but Miri emulates it via an extern static shim with a size of [48] bytes and alignment of [48] bytes/ +} diff --git a/src/tools/miri/tests/fail/extern_static_wrong_size.stderr b/src/tools/miri/tests/fail/extern_static_wrong_size.stderr new file mode 100644 index 0000000000000..a56eba09df98f --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static_wrong_size.stderr @@ -0,0 +1,14 @@ +error: unsupported operation: `extern` static `environ` from crate `extern_static_wrong_size` has been declared with a size of 1 bytes and alignment of 1 bytes, but Miri emulates it via an extern static shim with a size of N bytes and alignment of N bytes + --> $DIR/extern_static_wrong_size.rs:LL:CC + | +LL | let _val = unsafe { environ }; + | ^^^^^^^ `extern` static `environ` from crate `extern_static_wrong_size` has been declared with a size of 1 bytes and alignment of 1 bytes, but Miri emulates it via an extern static shim with a size of N bytes and alignment of N bytes + | + = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support + = note: BACKTRACE: + = note: inside `main` at $DIR/extern_static_wrong_size.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/fast_math_both.rs b/src/tools/miri/tests/fail/fast_math_both.rs new file mode 100644 index 0000000000000..dd2787bf40f40 --- /dev/null +++ b/src/tools/miri/tests/fail/fast_math_both.rs @@ -0,0 +1,7 @@ +#![feature(core_intrinsics)] + +fn main() { + unsafe { + let _x: f32 = core::intrinsics::fsub_fast(f32::NAN, f32::NAN); //~ ERROR: `fsub_fast` intrinsic called with non-finite value as both parameters + } +} diff --git a/src/tools/miri/tests/fail/fast_math_both.stderr b/src/tools/miri/tests/fail/fast_math_both.stderr new file mode 100644 index 0000000000000..2a0759f8a3ba7 --- /dev/null +++ b/src/tools/miri/tests/fail/fast_math_both.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: `fsub_fast` intrinsic called with non-finite value as both parameters + --> $DIR/fast_math_both.rs:LL:CC + | +LL | ...: f32 = core::intrinsics::fsub_fast(f32::NAN, f32::NAN); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `fsub_fast` intrinsic called with non-finite value as both parameters + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/fast_math_both.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/fast_math_first.rs b/src/tools/miri/tests/fail/fast_math_first.rs new file mode 100644 index 0000000000000..e495498ab2869 --- /dev/null +++ b/src/tools/miri/tests/fail/fast_math_first.rs @@ -0,0 +1,7 @@ +#![feature(core_intrinsics)] + +fn main() { + unsafe { + let _x: f32 = core::intrinsics::frem_fast(f32::NAN, 3.2); //~ ERROR: `frem_fast` intrinsic called with non-finite value as first parameter + } +} diff --git a/src/tools/miri/tests/fail/fast_math_first.stderr b/src/tools/miri/tests/fail/fast_math_first.stderr new file mode 100644 index 0000000000000..766662ca14ba9 --- /dev/null +++ b/src/tools/miri/tests/fail/fast_math_first.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: `frem_fast` intrinsic called with non-finite value as first parameter + --> $DIR/fast_math_first.rs:LL:CC + | +LL | ... let _x: f32 = core::intrinsics::frem_fast(f32::NAN, 3.2); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `frem_fast` intrinsic called with non-finite value as first parameter + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/fast_math_first.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/fast_math_second.rs b/src/tools/miri/tests/fail/fast_math_second.rs new file mode 100644 index 0000000000000..408c461077fc5 --- /dev/null +++ b/src/tools/miri/tests/fail/fast_math_second.rs @@ -0,0 +1,7 @@ +#![feature(core_intrinsics)] + +fn main() { + unsafe { + let _x: f32 = core::intrinsics::fmul_fast(3.4f32, f32::INFINITY); //~ ERROR: `fmul_fast` intrinsic called with non-finite value as second parameter + } +} diff --git a/src/tools/miri/tests/fail/fast_math_second.stderr b/src/tools/miri/tests/fail/fast_math_second.stderr new file mode 100644 index 0000000000000..ce93f9398f2cd --- /dev/null +++ b/src/tools/miri/tests/fail/fast_math_second.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: `fmul_fast` intrinsic called with non-finite value as second parameter + --> $DIR/fast_math_second.rs:LL:CC + | +LL | ...f32 = core::intrinsics::fmul_fast(3.4f32, f32::INFINITY); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `fmul_fast` intrinsic called with non-finite value as second parameter + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/fast_math_second.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/function_calls/check_arg_abi.rs b/src/tools/miri/tests/fail/function_calls/check_arg_abi.rs new file mode 100644 index 0000000000000..ffa0443ce507a --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/check_arg_abi.rs @@ -0,0 +1,9 @@ +fn main() { + extern "Rust" { + fn malloc(size: usize) -> *mut std::ffi::c_void; + } + + unsafe { + let _ = malloc(0); //~ ERROR: calling a function with ABI C using caller ABI Rust + }; +} diff --git a/src/tools/miri/tests/fail/function_calls/check_arg_abi.stderr b/src/tools/miri/tests/fail/function_calls/check_arg_abi.stderr new file mode 100644 index 0000000000000..406ccb070bab5 --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/check_arg_abi.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: calling a function with ABI C using caller ABI Rust + --> $DIR/check_arg_abi.rs:LL:CC + | +LL | let _ = malloc(0); + | ^^^^^^^^^ calling a function with ABI C using caller ABI Rust + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/check_arg_abi.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/function_calls/check_arg_count_abort.rs b/src/tools/miri/tests/fail/function_calls/check_arg_count_abort.rs new file mode 100644 index 0000000000000..967a78bf83187 --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/check_arg_count_abort.rs @@ -0,0 +1,10 @@ +fn main() { + extern "C" { + fn abort(_: i32) -> !; + } + + unsafe { + abort(1); + //~^ ERROR: Undefined Behavior: incorrect number of arguments: got 1, expected 0 + } +} diff --git a/src/tools/miri/tests/fail/function_calls/check_arg_count_abort.stderr b/src/tools/miri/tests/fail/function_calls/check_arg_count_abort.stderr new file mode 100644 index 0000000000000..d90a7e31d6ee9 --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/check_arg_count_abort.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: incorrect number of arguments: got 1, expected 0 + --> $DIR/check_arg_count_abort.rs:LL:CC + | +LL | abort(1); + | ^^^^^^^^ incorrect number of arguments: got 1, expected 0 + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/check_arg_count_abort.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/function_calls/check_arg_count_too_few_args.rs b/src/tools/miri/tests/fail/function_calls/check_arg_count_too_few_args.rs new file mode 100644 index 0000000000000..223c95ffca46b --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/check_arg_count_too_few_args.rs @@ -0,0 +1,9 @@ +fn main() { + extern "C" { + fn malloc() -> *mut std::ffi::c_void; + } + + unsafe { + let _ = malloc(); //~ ERROR: Undefined Behavior: incorrect number of arguments: got 0, expected 1 + }; +} diff --git a/src/tools/miri/tests/fail/function_calls/check_arg_count_too_few_args.stderr b/src/tools/miri/tests/fail/function_calls/check_arg_count_too_few_args.stderr new file mode 100644 index 0000000000000..9e2751a216bcb --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/check_arg_count_too_few_args.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: incorrect number of arguments: got 0, expected 1 + --> $DIR/check_arg_count_too_few_args.rs:LL:CC + | +LL | let _ = malloc(); + | ^^^^^^^^ incorrect number of arguments: got 0, expected 1 + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/check_arg_count_too_few_args.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/function_calls/check_arg_count_too_many_args.rs b/src/tools/miri/tests/fail/function_calls/check_arg_count_too_many_args.rs new file mode 100644 index 0000000000000..7ee9c40bf7a4b --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/check_arg_count_too_many_args.rs @@ -0,0 +1,9 @@ +fn main() { + extern "C" { + fn malloc(_: i32, _: i32) -> *mut std::ffi::c_void; + } + + unsafe { + let _ = malloc(1, 2); //~ ERROR: Undefined Behavior: incorrect number of arguments: got 2, expected 1 + }; +} diff --git a/src/tools/miri/tests/fail/function_calls/check_arg_count_too_many_args.stderr b/src/tools/miri/tests/fail/function_calls/check_arg_count_too_many_args.stderr new file mode 100644 index 0000000000000..e9a38b5ae4218 --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/check_arg_count_too_many_args.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: incorrect number of arguments: got 2, expected 1 + --> $DIR/check_arg_count_too_many_args.rs:LL:CC + | +LL | let _ = malloc(1, 2); + | ^^^^^^^^^^^^ incorrect number of arguments: got 2, expected 1 + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/check_arg_count_too_many_args.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/function_calls/check_callback_abi.rs b/src/tools/miri/tests/fail/function_calls/check_callback_abi.rs new file mode 100644 index 0000000000000..883d5ae8096bd --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/check_callback_abi.rs @@ -0,0 +1,18 @@ +#![feature(core_intrinsics)] + +extern "C" fn try_fn(_: *mut u8) { + unreachable!(); +} + +fn main() { + unsafe { + // Make sure we check the ABI when Miri itself invokes a function + // as part of a shim implementation. + std::intrinsics::r#try( + //~^ ERROR: calling a function with ABI C using caller ABI Rust + std::mem::transmute::(try_fn), + std::ptr::null_mut(), + |_, _| unreachable!(), + ); + } +} diff --git a/src/tools/miri/tests/fail/function_calls/check_callback_abi.stderr b/src/tools/miri/tests/fail/function_calls/check_callback_abi.stderr new file mode 100644 index 0000000000000..50afc10979749 --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/check_callback_abi.stderr @@ -0,0 +1,20 @@ +error: Undefined Behavior: calling a function with ABI C using caller ABI Rust + --> $DIR/check_callback_abi.rs:LL:CC + | +LL | / std::intrinsics::r#try( +LL | | +LL | | std::mem::transmute::(try_fn), +LL | | std::ptr::null_mut(), +LL | | |_, _| unreachable!(), +LL | | ); + | |_________^ calling a function with ABI C using caller ABI Rust + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/check_callback_abi.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/function_calls/exported_symbol_abi_mismatch.cache.stderr b/src/tools/miri/tests/fail/function_calls/exported_symbol_abi_mismatch.cache.stderr new file mode 100644 index 0000000000000..ae5c6cb72b3c9 --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_abi_mismatch.cache.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: calling a function with calling convention Rust using calling convention C + --> $DIR/exported_symbol_abi_mismatch.rs:LL:CC + | +LL | foo(); + | ^^^^^ calling a function with calling convention Rust using calling convention C + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/exported_symbol_abi_mismatch.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/function_calls/exported_symbol_abi_mismatch.fn_ptr.stderr b/src/tools/miri/tests/fail/function_calls/exported_symbol_abi_mismatch.fn_ptr.stderr new file mode 100644 index 0000000000000..17d56793ac5c6 --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_abi_mismatch.fn_ptr.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: calling a function with calling convention Rust using calling convention C + --> $DIR/exported_symbol_abi_mismatch.rs:LL:CC + | +LL | std::mem::transmute::(foo)(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ calling a function with calling convention Rust using calling convention C + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/exported_symbol_abi_mismatch.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/function_calls/exported_symbol_abi_mismatch.no_cache.stderr b/src/tools/miri/tests/fail/function_calls/exported_symbol_abi_mismatch.no_cache.stderr new file mode 100644 index 0000000000000..ae5c6cb72b3c9 --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_abi_mismatch.no_cache.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: calling a function with calling convention Rust using calling convention C + --> $DIR/exported_symbol_abi_mismatch.rs:LL:CC + | +LL | foo(); + | ^^^^^ calling a function with calling convention Rust using calling convention C + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/exported_symbol_abi_mismatch.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/function_calls/exported_symbol_abi_mismatch.rs b/src/tools/miri/tests/fail/function_calls/exported_symbol_abi_mismatch.rs new file mode 100644 index 0000000000000..dbf72b5b61ad9 --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_abi_mismatch.rs @@ -0,0 +1,35 @@ +//@revisions: no_cache cache fn_ptr + +#[no_mangle] +fn foo() {} + +fn main() { + #[cfg(any(cache, fn_ptr))] + extern "Rust" { + fn foo(); + } + + #[cfg(fn_ptr)] + unsafe { + std::mem::transmute::(foo)(); + //[fn_ptr]~^ ERROR: calling a function with calling convention Rust using calling convention C + } + + // `Instance` caching should not suppress ABI check. + #[cfg(cache)] + unsafe { + foo(); + } + + { + #[cfg_attr(any(cache, fn_ptr), allow(clashing_extern_declarations))] + extern "C" { + fn foo(); + } + unsafe { + foo(); + //[no_cache]~^ ERROR: calling a function with calling convention Rust using calling convention C + //[cache]~| ERROR: calling a function with calling convention Rust using calling convention C + } + } +} diff --git a/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind1.rs b/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind1.rs new file mode 100644 index 0000000000000..5f4df7c6a1ef3 --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind1.rs @@ -0,0 +1,15 @@ +//@compile-flags: -Zmiri-disable-abi-check +#![feature(c_unwind)] + +#[no_mangle] +extern "C-unwind" fn unwind() { + panic!(); +} + +fn main() { + extern "C" { + fn unwind(); + } + unsafe { unwind() } + //~^ ERROR: unwinding past a stack frame that does not allow unwinding +} diff --git a/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind1.stderr b/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind1.stderr new file mode 100644 index 0000000000000..7f87ec6f3bb69 --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind1.stderr @@ -0,0 +1,17 @@ +thread 'main' panicked at 'explicit panic', $DIR/exported_symbol_bad_unwind1.rs:LL:CC +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +error: Undefined Behavior: unwinding past a stack frame that does not allow unwinding + --> $DIR/exported_symbol_bad_unwind1.rs:LL:CC + | +LL | unsafe { unwind() } + | ^^^^^^^^ unwinding past a stack frame that does not allow unwinding + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/exported_symbol_bad_unwind1.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.both.stderr b/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.both.stderr new file mode 100644 index 0000000000000..7d9302e3e3adc --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.both.stderr @@ -0,0 +1,23 @@ +thread 'main' panicked at 'explicit panic', $DIR/exported_symbol_bad_unwind2.rs:LL:CC +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +error: abnormal termination: the program aborted execution + --> $DIR/exported_symbol_bad_unwind2.rs:LL:CC + | +LL | / extern "C-unwind" fn nounwind() { +LL | | +LL | | +LL | | panic!(); +LL | | } + | |_^ the program aborted execution + | + = note: inside `nounwind` at $DIR/exported_symbol_bad_unwind2.rs:LL:CC +note: inside `main` at $DIR/exported_symbol_bad_unwind2.rs:LL:CC + --> $DIR/exported_symbol_bad_unwind2.rs:LL:CC + | +LL | unsafe { nounwind() } + | ^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.definition.stderr b/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.definition.stderr new file mode 100644 index 0000000000000..7d9302e3e3adc --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.definition.stderr @@ -0,0 +1,23 @@ +thread 'main' panicked at 'explicit panic', $DIR/exported_symbol_bad_unwind2.rs:LL:CC +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +error: abnormal termination: the program aborted execution + --> $DIR/exported_symbol_bad_unwind2.rs:LL:CC + | +LL | / extern "C-unwind" fn nounwind() { +LL | | +LL | | +LL | | panic!(); +LL | | } + | |_^ the program aborted execution + | + = note: inside `nounwind` at $DIR/exported_symbol_bad_unwind2.rs:LL:CC +note: inside `main` at $DIR/exported_symbol_bad_unwind2.rs:LL:CC + --> $DIR/exported_symbol_bad_unwind2.rs:LL:CC + | +LL | unsafe { nounwind() } + | ^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.extern_block.stderr b/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.extern_block.stderr new file mode 100644 index 0000000000000..b23c05a530357 --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.extern_block.stderr @@ -0,0 +1,17 @@ +thread 'main' panicked at 'explicit panic', $DIR/exported_symbol_bad_unwind2.rs:LL:CC +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +error: Undefined Behavior: unwinding past a stack frame that does not allow unwinding + --> $DIR/exported_symbol_bad_unwind2.rs:LL:CC + | +LL | unsafe { nounwind() } + | ^^^^^^^^^^ unwinding past a stack frame that does not allow unwinding + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/exported_symbol_bad_unwind2.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.rs b/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.rs new file mode 100644 index 0000000000000..d9aacdb8aea4e --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.rs @@ -0,0 +1,19 @@ +//@revisions: extern_block definition both +#![feature(rustc_attrs, c_unwind)] + +#[cfg_attr(any(definition, both), rustc_allocator_nounwind)] +#[no_mangle] +extern "C-unwind" fn nounwind() { + //[definition]~^ ERROR: abnormal termination: the program aborted execution + //[both]~^^ ERROR: abnormal termination: the program aborted execution + panic!(); +} + +fn main() { + extern "C-unwind" { + #[cfg_attr(any(extern_block, both), rustc_allocator_nounwind)] + fn nounwind(); + } + unsafe { nounwind() } + //[extern_block]~^ ERROR: unwinding past a stack frame that does not allow unwinding +} diff --git a/src/tools/miri/tests/fail/function_calls/exported_symbol_clashing.rs b/src/tools/miri/tests/fail/function_calls/exported_symbol_clashing.rs new file mode 100644 index 0000000000000..45ad412e7a55e --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_clashing.rs @@ -0,0 +1,15 @@ +#[no_mangle] +fn foo() {} +//~^ HELP: it's first defined here, in crate `exported_symbol_clashing` + +#[export_name = "foo"] +fn bar() {} +//~^ HELP: then it's defined here again, in crate `exported_symbol_clashing` + +fn main() { + extern "Rust" { + fn foo(); + } + unsafe { foo() } + //~^ ERROR: multiple definitions of symbol `foo` +} diff --git a/src/tools/miri/tests/fail/function_calls/exported_symbol_clashing.stderr b/src/tools/miri/tests/fail/function_calls/exported_symbol_clashing.stderr new file mode 100644 index 0000000000000..8eb9fa4ff5c27 --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_clashing.stderr @@ -0,0 +1,23 @@ +error: multiple definitions of symbol `foo` + --> $DIR/exported_symbol_clashing.rs:LL:CC + | +LL | unsafe { foo() } + | ^^^^^ multiple definitions of symbol `foo` + | +help: it's first defined here, in crate `exported_symbol_clashing` + --> $DIR/exported_symbol_clashing.rs:LL:CC + | +LL | fn foo() {} + | ^^^^^^^^ +help: then it's defined here again, in crate `exported_symbol_clashing` + --> $DIR/exported_symbol_clashing.rs:LL:CC + | +LL | fn bar() {} + | ^^^^^^^^ + = note: BACKTRACE: + = note: inside `main` at $DIR/exported_symbol_clashing.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/function_calls/exported_symbol_shim_clashing.rs b/src/tools/miri/tests/fail/function_calls/exported_symbol_shim_clashing.rs new file mode 100644 index 0000000000000..dffae7adbb972 --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_shim_clashing.rs @@ -0,0 +1,15 @@ +#[no_mangle] +extern "C" fn malloc(_: usize) -> *mut std::ffi::c_void { + //~^ HELP: the `malloc` symbol is defined here + unreachable!() +} + +fn main() { + extern "C" { + fn malloc(_: usize) -> *mut std::ffi::c_void; + } + unsafe { + malloc(0); + //~^ ERROR: found `malloc` symbol definition that clashes with a built-in shim + } +} diff --git a/src/tools/miri/tests/fail/function_calls/exported_symbol_shim_clashing.stderr b/src/tools/miri/tests/fail/function_calls/exported_symbol_shim_clashing.stderr new file mode 100644 index 0000000000000..58a996e64530e --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_shim_clashing.stderr @@ -0,0 +1,21 @@ +error: found `malloc` symbol definition that clashes with a built-in shim + --> $DIR/exported_symbol_shim_clashing.rs:LL:CC + | +LL | malloc(0); + | ^^^^^^^^^ found `malloc` symbol definition that clashes with a built-in shim + | +help: the `malloc` symbol is defined here + --> $DIR/exported_symbol_shim_clashing.rs:LL:CC + | +LL | / extern "C" fn malloc(_: usize) -> *mut std::ffi::c_void { +LL | | +LL | | unreachable!() +LL | | } + | |_^ + = note: BACKTRACE: + = note: inside `main` at $DIR/exported_symbol_shim_clashing.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/function_calls/exported_symbol_wrong_arguments.rs b/src/tools/miri/tests/fail/function_calls/exported_symbol_wrong_arguments.rs new file mode 100644 index 0000000000000..a108944c5e434 --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_wrong_arguments.rs @@ -0,0 +1,9 @@ +#[no_mangle] +fn foo() {} + +fn main() { + extern "Rust" { + fn foo(_: i32); + } + unsafe { foo(1) } //~ ERROR: calling a function with more arguments than it expected +} diff --git a/src/tools/miri/tests/fail/function_calls/exported_symbol_wrong_arguments.stderr b/src/tools/miri/tests/fail/function_calls/exported_symbol_wrong_arguments.stderr new file mode 100644 index 0000000000000..1aa13ce438953 --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_wrong_arguments.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: calling a function with more arguments than it expected + --> $DIR/exported_symbol_wrong_arguments.rs:LL:CC + | +LL | unsafe { foo(1) } + | ^^^^^^ calling a function with more arguments than it expected + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/exported_symbol_wrong_arguments.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/function_calls/exported_symbol_wrong_type.rs b/src/tools/miri/tests/fail/function_calls/exported_symbol_wrong_type.rs new file mode 100644 index 0000000000000..e273e354334f8 --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_wrong_type.rs @@ -0,0 +1,9 @@ +#[no_mangle] +static FOO: () = (); + +fn main() { + extern "C" { + fn FOO(); + } + unsafe { FOO() } //~ ERROR: attempt to call an exported symbol that is not defined as a function +} diff --git a/src/tools/miri/tests/fail/function_calls/exported_symbol_wrong_type.stderr b/src/tools/miri/tests/fail/function_calls/exported_symbol_wrong_type.stderr new file mode 100644 index 0000000000000..abfd7a9a6c4d9 --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_wrong_type.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: attempt to call an exported symbol that is not defined as a function + --> $DIR/exported_symbol_wrong_type.rs:LL:CC + | +LL | unsafe { FOO() } + | ^^^^^ attempt to call an exported symbol that is not defined as a function + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/exported_symbol_wrong_type.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/function_pointers/cast_box_int_to_fn_ptr.rs b/src/tools/miri/tests/fail/function_pointers/cast_box_int_to_fn_ptr.rs new file mode 100644 index 0000000000000..9815569b607f2 --- /dev/null +++ b/src/tools/miri/tests/fail/function_pointers/cast_box_int_to_fn_ptr.rs @@ -0,0 +1,9 @@ +// Validation makes this fail in the wrong place +//@compile-flags: -Zmiri-disable-validation + +fn main() { + let b = Box::new(42); + let g = unsafe { std::mem::transmute::<&Box, &fn(i32)>(&b) }; + + (*g)(42) //~ ERROR: it does not point to a function +} diff --git a/src/tools/miri/tests/fail/function_pointers/cast_box_int_to_fn_ptr.stderr b/src/tools/miri/tests/fail/function_pointers/cast_box_int_to_fn_ptr.stderr new file mode 100644 index 0000000000000..ad43c2c9d3fe7 --- /dev/null +++ b/src/tools/miri/tests/fail/function_pointers/cast_box_int_to_fn_ptr.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: using ALLOC as function pointer but it does not point to a function + --> $DIR/cast_box_int_to_fn_ptr.rs:LL:CC + | +LL | (*g)(42) + | ^^^^^^^^ using ALLOC as function pointer but it does not point to a function + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/cast_box_int_to_fn_ptr.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr1.rs b/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr1.rs new file mode 100644 index 0000000000000..c0e96a43cc522 --- /dev/null +++ b/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr1.rs @@ -0,0 +1,7 @@ +fn main() { + fn f() {} + + let g = unsafe { std::mem::transmute::(f) }; + + g(42) //~ ERROR: calling a function with more arguments than it expected +} diff --git a/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr1.stderr b/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr1.stderr new file mode 100644 index 0000000000000..bb2a263795980 --- /dev/null +++ b/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr1.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: calling a function with more arguments than it expected + --> $DIR/cast_fn_ptr1.rs:LL:CC + | +LL | g(42) + | ^^^^^ calling a function with more arguments than it expected + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/cast_fn_ptr1.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr2.rs b/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr2.rs new file mode 100644 index 0000000000000..20384f0965b82 --- /dev/null +++ b/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr2.rs @@ -0,0 +1,7 @@ +fn main() { + fn f(_: (i32, i32)) {} + + let g = unsafe { std::mem::transmute::(f) }; + + g(42) //~ ERROR: calling a function with argument of type (i32, i32) passing data of type i32 +} diff --git a/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr2.stderr b/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr2.stderr new file mode 100644 index 0000000000000..086712e0d13bd --- /dev/null +++ b/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr2.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: calling a function with argument of type (i32, i32) passing data of type i32 + --> $DIR/cast_fn_ptr2.rs:LL:CC + | +LL | g(42) + | ^^^^^ calling a function with argument of type (i32, i32) passing data of type i32 + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/cast_fn_ptr2.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr3.rs b/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr3.rs new file mode 100644 index 0000000000000..920fb51abb644 --- /dev/null +++ b/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr3.rs @@ -0,0 +1,7 @@ +fn main() { + fn f(_: (i32, i32)) {} + + let g = unsafe { std::mem::transmute::(f) }; + + g() //~ ERROR: calling a function with fewer arguments than it requires +} diff --git a/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr3.stderr b/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr3.stderr new file mode 100644 index 0000000000000..55fd7d6072089 --- /dev/null +++ b/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr3.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: calling a function with fewer arguments than it requires + --> $DIR/cast_fn_ptr3.rs:LL:CC + | +LL | g() + | ^^^ calling a function with fewer arguments than it requires + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/cast_fn_ptr3.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr4.rs b/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr4.rs new file mode 100644 index 0000000000000..f0ea5ccfe0f5a --- /dev/null +++ b/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr4.rs @@ -0,0 +1,7 @@ +fn main() { + fn f(_: *const [i32]) {} + + let g = unsafe { std::mem::transmute::(f) }; + + g(&42 as *const i32) //~ ERROR: calling a function with argument of type *const [i32] passing data of type *const i32 +} diff --git a/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr4.stderr b/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr4.stderr new file mode 100644 index 0000000000000..610425658fe1f --- /dev/null +++ b/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr4.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: calling a function with argument of type *const [i32] passing data of type *const i32 + --> $DIR/cast_fn_ptr4.rs:LL:CC + | +LL | g(&42 as *const i32) + | ^^^^^^^^^^^^^^^^^^^^ calling a function with argument of type *const [i32] passing data of type *const i32 + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/cast_fn_ptr4.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr5.rs b/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr5.rs new file mode 100644 index 0000000000000..0fdab49b94b6f --- /dev/null +++ b/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr5.rs @@ -0,0 +1,9 @@ +fn main() { + fn f() -> u32 { + 42 + } + + let g = unsafe { std::mem::transmute:: u32, fn()>(f) }; + + g() //~ ERROR: calling a function with return type u32 passing return place of type () +} diff --git a/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr5.stderr b/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr5.stderr new file mode 100644 index 0000000000000..c4e08b58430a2 --- /dev/null +++ b/src/tools/miri/tests/fail/function_pointers/cast_fn_ptr5.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: calling a function with return type u32 passing return place of type () + --> $DIR/cast_fn_ptr5.rs:LL:CC + | +LL | g() + | ^^^ calling a function with return type u32 passing return place of type () + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/cast_fn_ptr5.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/function_pointers/cast_int_to_fn_ptr.rs b/src/tools/miri/tests/fail/function_pointers/cast_int_to_fn_ptr.rs new file mode 100644 index 0000000000000..dbf8a560fb7af --- /dev/null +++ b/src/tools/miri/tests/fail/function_pointers/cast_int_to_fn_ptr.rs @@ -0,0 +1,8 @@ +// Validation makes this fail in the wrong place +//@compile-flags: -Zmiri-disable-validation + +fn main() { + let g = unsafe { std::mem::transmute::(42) }; + + g(42) //~ ERROR: is a dangling pointer +} diff --git a/src/tools/miri/tests/fail/function_pointers/cast_int_to_fn_ptr.stderr b/src/tools/miri/tests/fail/function_pointers/cast_int_to_fn_ptr.stderr new file mode 100644 index 0000000000000..81fc9716a4156 --- /dev/null +++ b/src/tools/miri/tests/fail/function_pointers/cast_int_to_fn_ptr.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: out-of-bounds pointer use: 0x2a[noalloc] is a dangling pointer (it has no provenance) + --> $DIR/cast_int_to_fn_ptr.rs:LL:CC + | +LL | g(42) + | ^^^^^ out-of-bounds pointer use: 0x2a[noalloc] is a dangling pointer (it has no provenance) + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/cast_int_to_fn_ptr.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/function_pointers/deref_fn_ptr.rs b/src/tools/miri/tests/fail/function_pointers/deref_fn_ptr.rs new file mode 100644 index 0000000000000..f071b63902fee --- /dev/null +++ b/src/tools/miri/tests/fail/function_pointers/deref_fn_ptr.rs @@ -0,0 +1,8 @@ +fn f() {} + +fn main() { + let x: u8 = unsafe { + *std::mem::transmute::(f) //~ ERROR: out-of-bounds + }; + panic!("this should never print: {}", x); +} diff --git a/src/tools/miri/tests/fail/function_pointers/deref_fn_ptr.stderr b/src/tools/miri/tests/fail/function_pointers/deref_fn_ptr.stderr new file mode 100644 index 0000000000000..7ce0b08695ebb --- /dev/null +++ b/src/tools/miri/tests/fail/function_pointers/deref_fn_ptr.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: dereferencing pointer failed: ALLOC has size 0, so pointer to 1 byte starting at offset 0 is out-of-bounds + --> $DIR/deref_fn_ptr.rs:LL:CC + | +LL | *std::mem::transmute::(f) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ dereferencing pointer failed: ALLOC has size 0, so pointer to 1 byte starting at offset 0 is out-of-bounds + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/deref_fn_ptr.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/function_pointers/execute_memory.rs b/src/tools/miri/tests/fail/function_pointers/execute_memory.rs new file mode 100644 index 0000000000000..967933e769b8c --- /dev/null +++ b/src/tools/miri/tests/fail/function_pointers/execute_memory.rs @@ -0,0 +1,12 @@ +// Validation makes this fail in the wrong place +//@compile-flags: -Zmiri-disable-validation + +#![feature(box_syntax)] + +fn main() { + let x = box 42; + unsafe { + let f = std::mem::transmute::, fn()>(x); + f() //~ ERROR: function pointer but it does not point to a function + } +} diff --git a/src/tools/miri/tests/fail/function_pointers/execute_memory.stderr b/src/tools/miri/tests/fail/function_pointers/execute_memory.stderr new file mode 100644 index 0000000000000..10c53ca2beaee --- /dev/null +++ b/src/tools/miri/tests/fail/function_pointers/execute_memory.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: using ALLOC as function pointer but it does not point to a function + --> $DIR/execute_memory.rs:LL:CC + | +LL | f() + | ^^^ using ALLOC as function pointer but it does not point to a function + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/execute_memory.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/function_pointers/fn_ptr_offset.rs b/src/tools/miri/tests/fail/function_pointers/fn_ptr_offset.rs new file mode 100644 index 0000000000000..eba0953ac863f --- /dev/null +++ b/src/tools/miri/tests/fail/function_pointers/fn_ptr_offset.rs @@ -0,0 +1,14 @@ +// Validation makes this fail in the wrong place +//@compile-flags: -Zmiri-disable-validation + +use std::mem; + +fn f() {} + +fn main() { + let x: fn() = f; + let y: *mut u8 = unsafe { mem::transmute(x) }; + let y = y.wrapping_offset(1); + let x: fn() = unsafe { mem::transmute(y) }; + x(); //~ ERROR: function pointer but it does not point to a function +} diff --git a/src/tools/miri/tests/fail/function_pointers/fn_ptr_offset.stderr b/src/tools/miri/tests/fail/function_pointers/fn_ptr_offset.stderr new file mode 100644 index 0000000000000..f8c519c1b54b0 --- /dev/null +++ b/src/tools/miri/tests/fail/function_pointers/fn_ptr_offset.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: using ALLOC+0x1 as function pointer but it does not point to a function + --> $DIR/fn_ptr_offset.rs:LL:CC + | +LL | x(); + | ^^^ using ALLOC+0x1 as function pointer but it does not point to a function + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/fn_ptr_offset.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/generator-pinned-moved.rs b/src/tools/miri/tests/fail/generator-pinned-moved.rs new file mode 100644 index 0000000000000..240ae18cc45a6 --- /dev/null +++ b/src/tools/miri/tests/fail/generator-pinned-moved.rs @@ -0,0 +1,45 @@ +//@compile-flags: -Zmiri-disable-validation +#![feature(generators, generator_trait)] + +use std::{ + ops::{Generator, GeneratorState}, + pin::Pin, +}; + +fn firstn() -> impl Generator { + static move || { + let mut num = 0; + let num = &mut num; + + yield *num; + *num += 1; //~ ERROR: dereferenced after this allocation got freed + } +} + +struct GeneratorIteratorAdapter(G); + +impl Iterator for GeneratorIteratorAdapter +where + G: Generator, +{ + type Item = G::Yield; + + fn next(&mut self) -> Option { + let me = unsafe { Pin::new_unchecked(&mut self.0) }; + match me.resume(()) { + GeneratorState::Yielded(x) => Some(x), + GeneratorState::Complete(_) => None, + } + } +} + +fn main() { + let mut generator_iterator_2 = { + let mut generator_iterator = Box::new(GeneratorIteratorAdapter(firstn())); + generator_iterator.next(); // pin it + + Box::new(*generator_iterator) // move it + }; // *deallocate* generator_iterator + + generator_iterator_2.next(); // and use moved value +} diff --git a/src/tools/miri/tests/fail/generator-pinned-moved.stderr b/src/tools/miri/tests/fail/generator-pinned-moved.stderr new file mode 100644 index 0000000000000..4f73671a78947 --- /dev/null +++ b/src/tools/miri/tests/fail/generator-pinned-moved.stderr @@ -0,0 +1,26 @@ +error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed + --> $DIR/generator-pinned-moved.rs:LL:CC + | +LL | *num += 1; + | ^^^^^^^^^ pointer to ALLOC was dereferenced after this allocation got freed + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside closure at $DIR/generator-pinned-moved.rs:LL:CC +note: inside ` as std::iter::Iterator>::next` at $DIR/generator-pinned-moved.rs:LL:CC + --> $DIR/generator-pinned-moved.rs:LL:CC + | +LL | match me.resume(()) { + | ^^^^^^^^^^^^^ + = note: inside `> as std::iter::Iterator>::next` at RUSTLIB/alloc/src/boxed.rs:LL:CC +note: inside `main` at $DIR/generator-pinned-moved.rs:LL:CC + --> $DIR/generator-pinned-moved.rs:LL:CC + | +LL | generator_iterator_2.next(); // and use moved value + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/assume.rs b/src/tools/miri/tests/fail/intrinsics/assume.rs new file mode 100644 index 0000000000000..c34827427ef01 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/assume.rs @@ -0,0 +1,10 @@ +#![feature(core_intrinsics)] + +fn main() { + let x = 5; + unsafe { + std::intrinsics::assume(x < 10); + std::intrinsics::assume(x > 1); + std::intrinsics::assume(x > 42); //~ ERROR: `assume` called with `false` + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/assume.stderr b/src/tools/miri/tests/fail/intrinsics/assume.stderr new file mode 100644 index 0000000000000..c1909570d99f2 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/assume.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: `assume` called with `false` + --> $DIR/assume.rs:LL:CC + | +LL | std::intrinsics::assume(x > 42); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `assume` called with `false` + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/assume.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/copy_null.rs b/src/tools/miri/tests/fail/intrinsics/copy_null.rs new file mode 100644 index 0000000000000..237e517f2875a --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/copy_null.rs @@ -0,0 +1,15 @@ +#![feature(intrinsics)] + +// Directly call intrinsic to avoid debug assertions in libstd +extern "rust-intrinsic" { + fn copy_nonoverlapping(src: *const T, dst: *mut T, count: usize); +} + +fn main() { + let mut data = [0u16; 4]; + let ptr = &mut data[0] as *mut u16; + // Even copying 0 elements from NULL should error. + unsafe { + copy_nonoverlapping(std::ptr::null(), ptr, 0); //~ ERROR: memory access failed: null pointer is a dangling pointer + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/copy_null.stderr b/src/tools/miri/tests/fail/intrinsics/copy_null.stderr new file mode 100644 index 0000000000000..6e3215d9f1c9c --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/copy_null.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: memory access failed: null pointer is a dangling pointer (it has no provenance) + --> $DIR/copy_null.rs:LL:CC + | +LL | copy_nonoverlapping(std::ptr::null(), ptr, 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ memory access failed: null pointer is a dangling pointer (it has no provenance) + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/copy_null.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/copy_overflow.rs b/src/tools/miri/tests/fail/intrinsics/copy_overflow.rs new file mode 100644 index 0000000000000..e64a1f9090214 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/copy_overflow.rs @@ -0,0 +1,10 @@ +use std::mem; + +fn main() { + let x = 0; + let mut y = 0; + unsafe { + (&mut y as *mut i32).copy_from(&x, 1usize << (mem::size_of::() * 8 - 1)); + //~^ERROR: overflow computing total size + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/copy_overflow.stderr b/src/tools/miri/tests/fail/intrinsics/copy_overflow.stderr new file mode 100644 index 0000000000000..23a4adbd0ed6f --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/copy_overflow.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: overflow computing total size of `copy` + --> $DIR/copy_overflow.rs:LL:CC + | +LL | (&mut y as *mut i32).copy_from(&x, 1usize << (mem::size_of::() * 8 - 1)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflow computing total size of `copy` + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/copy_overflow.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/copy_overlapping.rs b/src/tools/miri/tests/fail/intrinsics/copy_overlapping.rs new file mode 100644 index 0000000000000..3df881bd43ca4 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/copy_overlapping.rs @@ -0,0 +1,15 @@ +#![feature(intrinsics)] + +// Directly call intrinsic to avoid debug assertions in libstd +extern "rust-intrinsic" { + fn copy_nonoverlapping(src: *const T, dst: *mut T, count: usize); +} + +fn main() { + let mut data = [0u8; 16]; + unsafe { + let a = data.as_mut_ptr(); + let b = a.wrapping_offset(1) as *mut _; + copy_nonoverlapping(a, b, 2); //~ ERROR: copy_nonoverlapping called on overlapping ranges + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/copy_overlapping.stderr b/src/tools/miri/tests/fail/intrinsics/copy_overlapping.stderr new file mode 100644 index 0000000000000..cdb3da74ca954 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/copy_overlapping.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: copy_nonoverlapping called on overlapping ranges + --> $DIR/copy_overlapping.rs:LL:CC + | +LL | copy_nonoverlapping(a, b, 2); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ copy_nonoverlapping called on overlapping ranges + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/copy_overlapping.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/copy_unaligned.rs b/src/tools/miri/tests/fail/intrinsics/copy_unaligned.rs new file mode 100644 index 0000000000000..281217f06f516 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/copy_unaligned.rs @@ -0,0 +1,15 @@ +#![feature(intrinsics)] + +// Directly call intrinsic to avoid debug assertions in libstd +extern "rust-intrinsic" { + fn copy_nonoverlapping(src: *const T, dst: *mut T, count: usize); +} + +fn main() { + let mut data = [0u16; 8]; + let ptr = (&mut data[0] as *mut u16 as *mut u8).wrapping_add(1) as *mut u16; + // Even copying 0 elements to something unaligned should error + unsafe { + copy_nonoverlapping(&data[5], ptr, 0); //~ ERROR: accessing memory with alignment 1, but alignment 2 is required + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/copy_unaligned.stderr b/src/tools/miri/tests/fail/intrinsics/copy_unaligned.stderr new file mode 100644 index 0000000000000..a275979e6be13 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/copy_unaligned.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: accessing memory with alignment ALIGN, but alignment ALIGN is required + --> $DIR/copy_unaligned.rs:LL:CC + | +LL | copy_nonoverlapping(&data[5], ptr, 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ accessing memory with alignment ALIGN, but alignment ALIGN is required + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/copy_unaligned.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/ctlz_nonzero.rs b/src/tools/miri/tests/fail/intrinsics/ctlz_nonzero.rs new file mode 100644 index 0000000000000..c26cd4cadb539 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/ctlz_nonzero.rs @@ -0,0 +1,15 @@ +#![feature(intrinsics)] + +mod rusti { + extern "rust-intrinsic" { + pub fn ctlz_nonzero(x: T) -> T; + } +} + +pub fn main() { + unsafe { + use crate::rusti::*; + + ctlz_nonzero(0u8); //~ ERROR: `ctlz_nonzero` called on 0 + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/ctlz_nonzero.stderr b/src/tools/miri/tests/fail/intrinsics/ctlz_nonzero.stderr new file mode 100644 index 0000000000000..5ae14472a8a63 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/ctlz_nonzero.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: `ctlz_nonzero` called on 0 + --> $DIR/ctlz_nonzero.rs:LL:CC + | +LL | ctlz_nonzero(0u8); + | ^^^^^^^^^^^^^^^^^ `ctlz_nonzero` called on 0 + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/ctlz_nonzero.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/cttz_nonzero.rs b/src/tools/miri/tests/fail/intrinsics/cttz_nonzero.rs new file mode 100644 index 0000000000000..25a0501fdd80e --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/cttz_nonzero.rs @@ -0,0 +1,15 @@ +#![feature(intrinsics)] + +mod rusti { + extern "rust-intrinsic" { + pub fn cttz_nonzero(x: T) -> T; + } +} + +pub fn main() { + unsafe { + use crate::rusti::*; + + cttz_nonzero(0u8); //~ ERROR: `cttz_nonzero` called on 0 + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/cttz_nonzero.stderr b/src/tools/miri/tests/fail/intrinsics/cttz_nonzero.stderr new file mode 100644 index 0000000000000..ae013fb3d9794 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/cttz_nonzero.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: `cttz_nonzero` called on 0 + --> $DIR/cttz_nonzero.rs:LL:CC + | +LL | cttz_nonzero(0u8); + | ^^^^^^^^^^^^^^^^^ `cttz_nonzero` called on 0 + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/cttz_nonzero.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/div-by-zero.rs b/src/tools/miri/tests/fail/intrinsics/div-by-zero.rs new file mode 100644 index 0000000000000..78c05c543a8f0 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/div-by-zero.rs @@ -0,0 +1,9 @@ +#![feature(core_intrinsics)] + +use std::intrinsics::*; + +fn main() { + unsafe { + let _n = unchecked_div(1i64, 0); //~ERROR: dividing by zero + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/div-by-zero.stderr b/src/tools/miri/tests/fail/intrinsics/div-by-zero.stderr new file mode 100644 index 0000000000000..8c2910de3eef4 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/div-by-zero.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: dividing by zero + --> $DIR/div-by-zero.rs:LL:CC + | +LL | let _n = unchecked_div(1i64, 0); + | ^^^^^^^^^^^^^^^^^^^^^^ dividing by zero + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/div-by-zero.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/exact_div1.rs b/src/tools/miri/tests/fail/intrinsics/exact_div1.rs new file mode 100644 index 0000000000000..3dda9d1090de7 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/exact_div1.rs @@ -0,0 +1,5 @@ +#![feature(core_intrinsics)] +fn main() { + // divison by 0 + unsafe { std::intrinsics::exact_div(2, 0) }; //~ ERROR: divisor of zero +} diff --git a/src/tools/miri/tests/fail/intrinsics/exact_div1.stderr b/src/tools/miri/tests/fail/intrinsics/exact_div1.stderr new file mode 100644 index 0000000000000..2c7bbc00e1b1b --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/exact_div1.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: calculating the remainder with a divisor of zero + --> $DIR/exact_div1.rs:LL:CC + | +LL | unsafe { std::intrinsics::exact_div(2, 0) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ calculating the remainder with a divisor of zero + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/exact_div1.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/exact_div2.rs b/src/tools/miri/tests/fail/intrinsics/exact_div2.rs new file mode 100644 index 0000000000000..00064fa0b9c11 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/exact_div2.rs @@ -0,0 +1,5 @@ +#![feature(core_intrinsics)] +fn main() { + // divison with a remainder + unsafe { std::intrinsics::exact_div(2u16, 3) }; //~ ERROR: 2_u16 cannot be divided by 3_u16 without remainder +} diff --git a/src/tools/miri/tests/fail/intrinsics/exact_div2.stderr b/src/tools/miri/tests/fail/intrinsics/exact_div2.stderr new file mode 100644 index 0000000000000..6a264b8b4476f --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/exact_div2.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: exact_div: 2_u16 cannot be divided by 3_u16 without remainder + --> $DIR/exact_div2.rs:LL:CC + | +LL | unsafe { std::intrinsics::exact_div(2u16, 3) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ exact_div: 2_u16 cannot be divided by 3_u16 without remainder + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/exact_div2.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/exact_div3.rs b/src/tools/miri/tests/fail/intrinsics/exact_div3.rs new file mode 100644 index 0000000000000..a61abcd137e17 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/exact_div3.rs @@ -0,0 +1,5 @@ +#![feature(core_intrinsics)] +fn main() { + // signed divison with a remainder + unsafe { std::intrinsics::exact_div(-19i8, 2) }; //~ ERROR: -19_i8 cannot be divided by 2_i8 without remainder +} diff --git a/src/tools/miri/tests/fail/intrinsics/exact_div3.stderr b/src/tools/miri/tests/fail/intrinsics/exact_div3.stderr new file mode 100644 index 0000000000000..1a73822c300f3 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/exact_div3.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: exact_div: -19_i8 cannot be divided by 2_i8 without remainder + --> $DIR/exact_div3.rs:LL:CC + | +LL | unsafe { std::intrinsics::exact_div(-19i8, 2) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ exact_div: -19_i8 cannot be divided by 2_i8 without remainder + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/exact_div3.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/exact_div4.rs b/src/tools/miri/tests/fail/intrinsics/exact_div4.rs new file mode 100644 index 0000000000000..b5b60190b4ece --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/exact_div4.rs @@ -0,0 +1,5 @@ +#![feature(core_intrinsics)] +fn main() { + // divison of MIN by -1 + unsafe { std::intrinsics::exact_div(i64::MIN, -1) }; //~ ERROR: overflow in signed remainder (dividing MIN by -1) +} diff --git a/src/tools/miri/tests/fail/intrinsics/exact_div4.stderr b/src/tools/miri/tests/fail/intrinsics/exact_div4.stderr new file mode 100644 index 0000000000000..27201d9c7cf65 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/exact_div4.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: overflow in signed remainder (dividing MIN by -1) + --> $DIR/exact_div4.rs:LL:CC + | +LL | unsafe { std::intrinsics::exact_div(i64::MIN, -1) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflow in signed remainder (dividing MIN by -1) + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/exact_div4.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_32_inf1.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_inf1.rs new file mode 100644 index 0000000000000..a57845426d582 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_inf1.rs @@ -0,0 +1,12 @@ +#![feature(intrinsics)] + +// Directly call intrinsic to avoid debug assertions in libstd +extern "rust-intrinsic" { + fn float_to_int_unchecked(value: Float) -> Int; +} + +fn main() { + unsafe { + float_to_int_unchecked::(f32::INFINITY); //~ ERROR: cannot be represented in target type `i32` + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_32_inf1.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_inf1.stderr new file mode 100644 index 0000000000000..c82d6b30224fc --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_inf1.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on +Inf which cannot be represented in target type `i32` + --> $DIR/float_to_int_32_inf1.rs:LL:CC + | +LL | float_to_int_unchecked::(f32::INFINITY); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on +Inf which cannot be represented in target type `i32` + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/float_to_int_32_inf1.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_32_infneg1.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_infneg1.rs new file mode 100644 index 0000000000000..d383fc5b50ac1 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_infneg1.rs @@ -0,0 +1,12 @@ +#![feature(intrinsics)] + +// Directly call intrinsic to avoid debug assertions in libstd +extern "rust-intrinsic" { + fn float_to_int_unchecked(value: Float) -> Int; +} + +fn main() { + unsafe { + float_to_int_unchecked::(f32::NEG_INFINITY); //~ ERROR: cannot be represented in target type `i32` + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_32_infneg1.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_infneg1.stderr new file mode 100644 index 0000000000000..4ca41b676e9c3 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_infneg1.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on -Inf which cannot be represented in target type `i32` + --> $DIR/float_to_int_32_infneg1.rs:LL:CC + | +LL | float_to_int_unchecked::(f32::NEG_INFINITY); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on -Inf which cannot be represented in target type `i32` + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/float_to_int_32_infneg1.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_32_nan.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_nan.rs new file mode 100644 index 0000000000000..a39a5066b6f8b --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_nan.rs @@ -0,0 +1,12 @@ +#![feature(intrinsics)] + +// Directly call intrinsic to avoid debug assertions in libstd +extern "rust-intrinsic" { + fn float_to_int_unchecked(value: Float) -> Int; +} + +fn main() { + unsafe { + float_to_int_unchecked::(f32::NAN); //~ ERROR: cannot be represented in target type `u32` + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_32_nan.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_nan.stderr new file mode 100644 index 0000000000000..88b8948b0c29e --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_nan.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on NaN which cannot be represented in target type `u32` + --> $DIR/float_to_int_32_nan.rs:LL:CC + | +LL | float_to_int_unchecked::(f32::NAN); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on NaN which cannot be represented in target type `u32` + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/float_to_int_32_nan.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_32_nanneg.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_nanneg.rs new file mode 100644 index 0000000000000..71436eb3ba846 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_nanneg.rs @@ -0,0 +1,12 @@ +#![feature(intrinsics)] + +// Directly call intrinsic to avoid debug assertions in libstd +extern "rust-intrinsic" { + fn float_to_int_unchecked(value: Float) -> Int; +} + +fn main() { + unsafe { + float_to_int_unchecked::(-f32::NAN); //~ ERROR: cannot be represented in target type `u32` + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_32_nanneg.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_nanneg.stderr new file mode 100644 index 0000000000000..ca798dd391aa9 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_nanneg.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on NaN which cannot be represented in target type `u32` + --> $DIR/float_to_int_32_nanneg.rs:LL:CC + | +LL | float_to_int_unchecked::(-f32::NAN); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on NaN which cannot be represented in target type `u32` + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/float_to_int_32_nanneg.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_32_neg.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_neg.rs new file mode 100644 index 0000000000000..98ba964e47c2a --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_neg.rs @@ -0,0 +1,12 @@ +#![feature(intrinsics)] + +// Directly call intrinsic to avoid debug assertions in libstd +extern "rust-intrinsic" { + fn float_to_int_unchecked(value: Float) -> Int; +} + +fn main() { + unsafe { + float_to_int_unchecked::(-1.000000001f32); //~ ERROR: cannot be represented in target type `u32` + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_32_neg.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_neg.stderr new file mode 100644 index 0000000000000..4ff6eb8098540 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_neg.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on -1 which cannot be represented in target type `u32` + --> $DIR/float_to_int_32_neg.rs:LL:CC + | +LL | float_to_int_unchecked::(-1.000000001f32); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on -1 which cannot be represented in target type `u32` + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/float_to_int_32_neg.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_32_too_big1.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_too_big1.rs new file mode 100644 index 0000000000000..424b8fd965e9c --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_too_big1.rs @@ -0,0 +1,12 @@ +#![feature(intrinsics)] + +// Directly call intrinsic to avoid debug assertions in libstd +extern "rust-intrinsic" { + fn float_to_int_unchecked(value: Float) -> Int; +} + +fn main() { + unsafe { + float_to_int_unchecked::(2147483648.0f32); //~ ERROR: cannot be represented in target type `i32` + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_32_too_big1.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_too_big1.stderr new file mode 100644 index 0000000000000..fd17709d164b1 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_too_big1.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on 2.14748365E+9 which cannot be represented in target type `i32` + --> $DIR/float_to_int_32_too_big1.rs:LL:CC + | +LL | float_to_int_unchecked::(2147483648.0f32); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on 2.14748365E+9 which cannot be represented in target type `i32` + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/float_to_int_32_too_big1.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_32_too_big2.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_too_big2.rs new file mode 100644 index 0000000000000..5c50926c4df3e --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_too_big2.rs @@ -0,0 +1,12 @@ +#![feature(intrinsics)] + +// Directly call intrinsic to avoid debug assertions in libstd +extern "rust-intrinsic" { + fn float_to_int_unchecked(value: Float) -> Int; +} + +fn main() { + unsafe { + float_to_int_unchecked::((u32::MAX - 127) as f32); //~ ERROR: cannot be represented in target type `u32` + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_32_too_big2.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_too_big2.stderr new file mode 100644 index 0000000000000..fdc1f65dc1485 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_too_big2.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on 4.2949673E+9 which cannot be represented in target type `u32` + --> $DIR/float_to_int_32_too_big2.rs:LL:CC + | +LL | float_to_int_unchecked::((u32::MAX - 127) as f32); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on 4.2949673E+9 which cannot be represented in target type `u32` + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/float_to_int_32_too_big2.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_32_too_small1.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_too_small1.rs new file mode 100644 index 0000000000000..e0abd19d03fc9 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_too_small1.rs @@ -0,0 +1,12 @@ +#![feature(intrinsics)] + +// Directly call intrinsic to avoid debug assertions in libstd +extern "rust-intrinsic" { + fn float_to_int_unchecked(value: Float) -> Int; +} + +fn main() { + unsafe { + float_to_int_unchecked::(-2147483904.0f32); //~ ERROR: cannot be represented in target type `i32` + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_32_too_small1.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_too_small1.stderr new file mode 100644 index 0000000000000..9e743a3214449 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_32_too_small1.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on -2.1474839E+9 which cannot be represented in target type `i32` + --> $DIR/float_to_int_32_too_small1.rs:LL:CC + | +LL | float_to_int_unchecked::(-2147483904.0f32); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on -2.1474839E+9 which cannot be represented in target type `i32` + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/float_to_int_32_too_small1.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_inf1.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_inf1.rs new file mode 100644 index 0000000000000..f5f842e58ece4 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_inf1.rs @@ -0,0 +1,12 @@ +#![feature(intrinsics)] + +// Directly call intrinsic to avoid debug assertions in libstd +extern "rust-intrinsic" { + fn float_to_int_unchecked(value: Float) -> Int; +} + +fn main() { + unsafe { + float_to_int_unchecked::(f64::INFINITY); //~ ERROR: cannot be represented in target type `u128` + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_inf1.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_inf1.stderr new file mode 100644 index 0000000000000..ee01143dc8dfc --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_inf1.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on +Inf which cannot be represented in target type `u128` + --> $DIR/float_to_int_64_inf1.rs:LL:CC + | +LL | float_to_int_unchecked::(f64::INFINITY); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on +Inf which cannot be represented in target type `u128` + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/float_to_int_64_inf1.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_infneg1.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_infneg1.rs new file mode 100644 index 0000000000000..244c25b31cbfc --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_infneg1.rs @@ -0,0 +1,12 @@ +#![feature(intrinsics)] + +// Directly call intrinsic to avoid debug assertions in libstd +extern "rust-intrinsic" { + fn float_to_int_unchecked(value: Float) -> Int; +} + +fn main() { + unsafe { + float_to_int_unchecked::(f64::NEG_INFINITY); //~ ERROR: cannot be represented in target type `u128` + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_infneg1.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_infneg1.stderr new file mode 100644 index 0000000000000..f37b8ae550643 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_infneg1.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on -Inf which cannot be represented in target type `u128` + --> $DIR/float_to_int_64_infneg1.rs:LL:CC + | +LL | float_to_int_unchecked::(f64::NEG_INFINITY); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on -Inf which cannot be represented in target type `u128` + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/float_to_int_64_infneg1.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_infneg2.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_infneg2.rs new file mode 100644 index 0000000000000..f7a663d12a56c --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_infneg2.rs @@ -0,0 +1,12 @@ +#![feature(intrinsics)] + +// Directly call intrinsic to avoid debug assertions in libstd +extern "rust-intrinsic" { + fn float_to_int_unchecked(value: Float) -> Int; +} + +fn main() { + unsafe { + float_to_int_unchecked::(f64::NEG_INFINITY); //~ ERROR: cannot be represented in target type `i128` + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_infneg2.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_infneg2.stderr new file mode 100644 index 0000000000000..05dcd5ebcf69a --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_infneg2.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on -Inf which cannot be represented in target type `i128` + --> $DIR/float_to_int_64_infneg2.rs:LL:CC + | +LL | float_to_int_unchecked::(f64::NEG_INFINITY); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on -Inf which cannot be represented in target type `i128` + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/float_to_int_64_infneg2.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_nan.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_nan.rs new file mode 100644 index 0000000000000..171cbcc59344f --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_nan.rs @@ -0,0 +1,12 @@ +#![feature(intrinsics)] + +// Directly call intrinsic to avoid debug assertions in libstd +extern "rust-intrinsic" { + fn float_to_int_unchecked(value: Float) -> Int; +} + +fn main() { + unsafe { + float_to_int_unchecked::(f64::NAN); //~ ERROR: cannot be represented in target type `u32` + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_nan.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_nan.stderr new file mode 100644 index 0000000000000..0a914abb2ce78 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_nan.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on NaN which cannot be represented in target type `u32` + --> $DIR/float_to_int_64_nan.rs:LL:CC + | +LL | float_to_int_unchecked::(f64::NAN); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on NaN which cannot be represented in target type `u32` + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/float_to_int_64_nan.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_neg.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_neg.rs new file mode 100644 index 0000000000000..40b67e173b97a --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_neg.rs @@ -0,0 +1,12 @@ +#![feature(intrinsics)] + +// Directly call intrinsic to avoid debug assertions in libstd +extern "rust-intrinsic" { + fn float_to_int_unchecked(value: Float) -> Int; +} + +fn main() { + unsafe { + float_to_int_unchecked::(-1.0000000000001f64); //~ ERROR: cannot be represented in target type `u128` + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_neg.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_neg.stderr new file mode 100644 index 0000000000000..7e24f45f653d1 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_neg.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on -1 which cannot be represented in target type `u128` + --> $DIR/float_to_int_64_neg.rs:LL:CC + | +LL | float_to_int_unchecked::(-1.0000000000001f64); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on -1 which cannot be represented in target type `u128` + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/float_to_int_64_neg.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big1.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big1.rs new file mode 100644 index 0000000000000..e785123c4ca91 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big1.rs @@ -0,0 +1,12 @@ +#![feature(intrinsics)] + +// Directly call intrinsic to avoid debug assertions in libstd +extern "rust-intrinsic" { + fn float_to_int_unchecked(value: Float) -> Int; +} + +fn main() { + unsafe { + float_to_int_unchecked::(2147483648.0f64); //~ ERROR: cannot be represented in target type `i32` + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big1.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big1.stderr new file mode 100644 index 0000000000000..42da33321f371 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big1.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on 2147483648 which cannot be represented in target type `i32` + --> $DIR/float_to_int_64_too_big1.rs:LL:CC + | +LL | float_to_int_unchecked::(2147483648.0f64); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on 2147483648 which cannot be represented in target type `i32` + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/float_to_int_64_too_big1.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big2.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big2.rs new file mode 100644 index 0000000000000..4bf31d8ac0271 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big2.rs @@ -0,0 +1,12 @@ +#![feature(intrinsics)] + +// Directly call intrinsic to avoid debug assertions in libstd +extern "rust-intrinsic" { + fn float_to_int_unchecked(value: Float) -> Int; +} + +fn main() { + unsafe { + float_to_int_unchecked::(9223372036854775808.0f64); //~ ERROR: cannot be represented in target type `i64` + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big2.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big2.stderr new file mode 100644 index 0000000000000..af4c4ceb3f73f --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big2.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on 9.2233720368547758E+18 which cannot be represented in target type `i64` + --> $DIR/float_to_int_64_too_big2.rs:LL:CC + | +LL | float_to_int_unchecked::(9223372036854775808.0f64); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on 9.2233720368547758E+18 which cannot be represented in target type `i64` + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/float_to_int_64_too_big2.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big3.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big3.rs new file mode 100644 index 0000000000000..9775a56724bc2 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big3.rs @@ -0,0 +1,12 @@ +#![feature(intrinsics)] + +// Directly call intrinsic to avoid debug assertions in libstd +extern "rust-intrinsic" { + fn float_to_int_unchecked(value: Float) -> Int; +} + +fn main() { + unsafe { + float_to_int_unchecked::(18446744073709551616.0f64); //~ ERROR: cannot be represented in target type `u64` + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big3.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big3.stderr new file mode 100644 index 0000000000000..6e384a6fbc7cb --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big3.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on 1.8446744073709552E+19 which cannot be represented in target type `u64` + --> $DIR/float_to_int_64_too_big3.rs:LL:CC + | +LL | float_to_int_unchecked::(18446744073709551616.0f64); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on 1.8446744073709552E+19 which cannot be represented in target type `u64` + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/float_to_int_64_too_big3.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big4.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big4.rs new file mode 100644 index 0000000000000..53ff06e1e4678 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big4.rs @@ -0,0 +1,12 @@ +#![feature(intrinsics)] + +// Directly call intrinsic to avoid debug assertions in libstd +extern "rust-intrinsic" { + fn float_to_int_unchecked(value: Float) -> Int; +} + +fn main() { + unsafe { + float_to_int_unchecked::(u128::MAX as f64); //~ ERROR: cannot be represented in target type `u128` + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big4.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big4.stderr new file mode 100644 index 0000000000000..77f05ff91e3b5 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big4.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on 3.4028236692093846E+38 which cannot be represented in target type `u128` + --> $DIR/float_to_int_64_too_big4.rs:LL:CC + | +LL | float_to_int_unchecked::(u128::MAX as f64); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on 3.4028236692093846E+38 which cannot be represented in target type `u128` + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/float_to_int_64_too_big4.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big5.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big5.rs new file mode 100644 index 0000000000000..44356ff1771b1 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big5.rs @@ -0,0 +1,12 @@ +#![feature(intrinsics)] + +// Directly call intrinsic to avoid debug assertions in libstd +extern "rust-intrinsic" { + fn float_to_int_unchecked(value: Float) -> Int; +} + +fn main() { + unsafe { + float_to_int_unchecked::(240282366920938463463374607431768211455.0f64); //~ ERROR: cannot be represented in target type `i128` + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big5.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big5.stderr new file mode 100644 index 0000000000000..cb5eba490b447 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big5.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on 2.4028236692093845E+38 which cannot be represented in target type `i128` + --> $DIR/float_to_int_64_too_big5.rs:LL:CC + | +LL | float_to_int_unchecked::(240282366920938463463374607431768211455.0f64); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on 2.4028236692093845E+38 which cannot be represented in target type `i128` + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/float_to_int_64_too_big5.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big6.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big6.rs new file mode 100644 index 0000000000000..66f5be96bfd0e --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big6.rs @@ -0,0 +1,12 @@ +#![feature(intrinsics)] + +// Directly call intrinsic to avoid debug assertions in libstd +extern "rust-intrinsic" { + fn float_to_int_unchecked(value: Float) -> Int; +} + +fn main() { + unsafe { + float_to_int_unchecked::(f64::MAX); //~ ERROR: cannot be represented in target type `u128` + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big6.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big6.stderr new file mode 100644 index 0000000000000..d899d2f808a5d --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big6.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on 1.7976931348623157E+308 which cannot be represented in target type `u128` + --> $DIR/float_to_int_64_too_big6.rs:LL:CC + | +LL | float_to_int_unchecked::(f64::MAX); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on 1.7976931348623157E+308 which cannot be represented in target type `u128` + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/float_to_int_64_too_big6.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big7.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big7.rs new file mode 100644 index 0000000000000..18b380e8575ee --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big7.rs @@ -0,0 +1,12 @@ +#![feature(intrinsics)] + +// Directly call intrinsic to avoid debug assertions in libstd +extern "rust-intrinsic" { + fn float_to_int_unchecked(value: Float) -> Int; +} + +fn main() { + unsafe { + float_to_int_unchecked::(f64::MIN); //~ ERROR: cannot be represented in target type `i128` + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big7.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big7.stderr new file mode 100644 index 0000000000000..443b2759c2606 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_big7.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on -1.7976931348623157E+308 which cannot be represented in target type `i128` + --> $DIR/float_to_int_64_too_big7.rs:LL:CC + | +LL | float_to_int_unchecked::(f64::MIN); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on -1.7976931348623157E+308 which cannot be represented in target type `i128` + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/float_to_int_64_too_big7.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_small1.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_small1.rs new file mode 100644 index 0000000000000..2a23b1dc8a4b8 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_small1.rs @@ -0,0 +1,12 @@ +#![feature(intrinsics)] + +// Directly call intrinsic to avoid debug assertions in libstd +extern "rust-intrinsic" { + fn float_to_int_unchecked(value: Float) -> Int; +} + +fn main() { + unsafe { + float_to_int_unchecked::(-2147483649.0f64); //~ ERROR: cannot be represented in target type `i32` + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_small1.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_small1.stderr new file mode 100644 index 0000000000000..f8d88c44aa80d --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_small1.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on -2147483649 which cannot be represented in target type `i32` + --> $DIR/float_to_int_64_too_small1.rs:LL:CC + | +LL | float_to_int_unchecked::(-2147483649.0f64); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on -2147483649 which cannot be represented in target type `i32` + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/float_to_int_64_too_small1.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_small2.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_small2.rs new file mode 100644 index 0000000000000..7fc3effda5dff --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_small2.rs @@ -0,0 +1,12 @@ +#![feature(intrinsics)] + +// Directly call intrinsic to avoid debug assertions in libstd +extern "rust-intrinsic" { + fn float_to_int_unchecked(value: Float) -> Int; +} + +fn main() { + unsafe { + float_to_int_unchecked::(-9223372036854777856.0f64); //~ ERROR: cannot be represented in target type `i64` + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_small2.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_small2.stderr new file mode 100644 index 0000000000000..d94e57b1e67c2 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_small2.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on -9.2233720368547778E+18 which cannot be represented in target type `i64` + --> $DIR/float_to_int_64_too_small2.rs:LL:CC + | +LL | float_to_int_unchecked::(-9223372036854777856.0f64); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on -9.2233720368547778E+18 which cannot be represented in target type `i64` + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/float_to_int_64_too_small2.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_small3.rs b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_small3.rs new file mode 100644 index 0000000000000..2a8f9c366425d --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_small3.rs @@ -0,0 +1,12 @@ +#![feature(intrinsics)] + +// Directly call intrinsic to avoid debug assertions in libstd +extern "rust-intrinsic" { + fn float_to_int_unchecked(value: Float) -> Int; +} + +fn main() { + unsafe { + float_to_int_unchecked::(-240282366920938463463374607431768211455.0f64); //~ ERROR: cannot be represented in target type `i128` + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_small3.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_small3.stderr new file mode 100644 index 0000000000000..59b74f5f51f37 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_too_small3.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on -2.4028236692093845E+38 which cannot be represented in target type `i128` + --> $DIR/float_to_int_64_too_small3.rs:LL:CC + | +LL | float_to_int_unchecked::(-240282366920938463463374607431768211455.0f64); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on -2.4028236692093845E+38 which cannot be represented in target type `i128` + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/float_to_int_64_too_small3.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/out_of_bounds_ptr_1.rs b/src/tools/miri/tests/fail/intrinsics/out_of_bounds_ptr_1.rs new file mode 100644 index 0000000000000..b6a110ee84d2c --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/out_of_bounds_ptr_1.rs @@ -0,0 +1,7 @@ +fn main() { + let v = [0i8; 4]; + let x = &v as *const i8; + // The error is inside another function, so we cannot match it by line + let x = unsafe { x.offset(5) }; //~ERROR: pointer to 5 bytes starting at offset 0 is out-of-bounds + panic!("this should never print: {:?}", x); +} diff --git a/src/tools/miri/tests/fail/intrinsics/out_of_bounds_ptr_1.stderr b/src/tools/miri/tests/fail/intrinsics/out_of_bounds_ptr_1.stderr new file mode 100644 index 0000000000000..4422310870a64 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/out_of_bounds_ptr_1.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: out-of-bounds pointer arithmetic: ALLOC has size 4, so pointer to 5 bytes starting at offset 0 is out-of-bounds + --> $DIR/out_of_bounds_ptr_1.rs:LL:CC + | +LL | let x = unsafe { x.offset(5) }; + | ^^^^^^^^^^^ out-of-bounds pointer arithmetic: ALLOC has size 4, so pointer to 5 bytes starting at offset 0 is out-of-bounds + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/out_of_bounds_ptr_1.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/out_of_bounds_ptr_2.rs b/src/tools/miri/tests/fail/intrinsics/out_of_bounds_ptr_2.rs new file mode 100644 index 0000000000000..0d4eea9a5bdea --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/out_of_bounds_ptr_2.rs @@ -0,0 +1,6 @@ +fn main() { + let v = [0i8; 4]; + let x = &v as *const i8; + let x = unsafe { x.offset(isize::MIN) }; //~ERROR: overflowing in-bounds pointer arithmetic + panic!("this should never print: {:?}", x); +} diff --git a/src/tools/miri/tests/fail/intrinsics/out_of_bounds_ptr_2.stderr b/src/tools/miri/tests/fail/intrinsics/out_of_bounds_ptr_2.stderr new file mode 100644 index 0000000000000..6a11ebae108f0 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/out_of_bounds_ptr_2.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: overflowing in-bounds pointer arithmetic + --> $DIR/out_of_bounds_ptr_2.rs:LL:CC + | +LL | let x = unsafe { x.offset(isize::MIN) }; + | ^^^^^^^^^^^^^^^^^^^^ overflowing in-bounds pointer arithmetic + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/out_of_bounds_ptr_2.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/out_of_bounds_ptr_3.rs b/src/tools/miri/tests/fail/intrinsics/out_of_bounds_ptr_3.rs new file mode 100644 index 0000000000000..701bc33a645e1 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/out_of_bounds_ptr_3.rs @@ -0,0 +1,6 @@ +fn main() { + let v = [0i8; 4]; + let x = &v as *const i8; + let x = unsafe { x.offset(-1) }; //~ERROR: pointer to 1 byte starting at offset -1 is out-of-bounds + panic!("this should never print: {:?}", x); +} diff --git a/src/tools/miri/tests/fail/intrinsics/out_of_bounds_ptr_3.stderr b/src/tools/miri/tests/fail/intrinsics/out_of_bounds_ptr_3.stderr new file mode 100644 index 0000000000000..1364e0f9009d8 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/out_of_bounds_ptr_3.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: out-of-bounds pointer arithmetic: ALLOC has size 4, so pointer to 1 byte starting at offset -1 is out-of-bounds + --> $DIR/out_of_bounds_ptr_3.rs:LL:CC + | +LL | let x = unsafe { x.offset(-1) }; + | ^^^^^^^^^^^^ out-of-bounds pointer arithmetic: ALLOC has size 4, so pointer to 1 byte starting at offset -1 is out-of-bounds + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/out_of_bounds_ptr_3.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/overflowing-unchecked-rsh.rs b/src/tools/miri/tests/fail/intrinsics/overflowing-unchecked-rsh.rs new file mode 100644 index 0000000000000..fe2e85be69868 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/overflowing-unchecked-rsh.rs @@ -0,0 +1,8 @@ +#![feature(unchecked_math)] + +fn main() { + unsafe { + let _n = 1i64.unchecked_shr(64); + //~^ ERROR: overflowing shift by 64 in `unchecked_shr` + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/overflowing-unchecked-rsh.stderr b/src/tools/miri/tests/fail/intrinsics/overflowing-unchecked-rsh.stderr new file mode 100644 index 0000000000000..9c5d0d13108ce --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/overflowing-unchecked-rsh.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: overflowing shift by 64 in `unchecked_shr` + --> $DIR/overflowing-unchecked-rsh.rs:LL:CC + | +LL | let _n = 1i64.unchecked_shr(64); + | ^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by 64 in `unchecked_shr` + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/overflowing-unchecked-rsh.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/ptr_offset_0_plus_0.rs b/src/tools/miri/tests/fail/intrinsics/ptr_offset_0_plus_0.rs new file mode 100644 index 0000000000000..e2329c1313984 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/ptr_offset_0_plus_0.rs @@ -0,0 +1,9 @@ +//@compile-flags: -Zmiri-permissive-provenance + +#[rustfmt::skip] // fails with "left behind trailing whitespace" +fn main() { + let x = 0 as *mut i32; + let _x = x.wrapping_offset(8); // ok, this has no inbounds tag + let _x = unsafe { x.offset(0) }; // UB despite offset 0, NULL is never inbounds + //~^ERROR: null pointer is a dangling pointer +} diff --git a/src/tools/miri/tests/fail/intrinsics/ptr_offset_0_plus_0.stderr b/src/tools/miri/tests/fail/intrinsics/ptr_offset_0_plus_0.stderr new file mode 100644 index 0000000000000..9c1c387d54991 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/ptr_offset_0_plus_0.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: out-of-bounds pointer arithmetic: null pointer is a dangling pointer (it has no provenance) + --> $DIR/ptr_offset_0_plus_0.rs:LL:CC + | +LL | let _x = unsafe { x.offset(0) }; // UB despite offset 0, NULL is never inbounds + | ^^^^^^^^^^^ out-of-bounds pointer arithmetic: null pointer is a dangling pointer (it has no provenance) + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/ptr_offset_0_plus_0.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_oob.rs b/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_oob.rs new file mode 100644 index 0000000000000..0e5acf08b2030 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_oob.rs @@ -0,0 +1,7 @@ +fn main() { + let start_ptr = &4 as *const _ as *const u8; + let length = 10; + let end_ptr = start_ptr.wrapping_add(length); + // Even if the offset is 0, a dangling OOB pointer is not allowed. + unsafe { end_ptr.offset_from(end_ptr) }; //~ERROR: pointer at offset 10 is out-of-bounds +} diff --git a/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_oob.stderr b/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_oob.stderr new file mode 100644 index 0000000000000..a31b929d7a7ae --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_oob.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: out-of-bounds offset_from: ALLOC has size 4, so pointer at offset 10 is out-of-bounds + --> $DIR/ptr_offset_from_oob.rs:LL:CC + | +LL | unsafe { end_ptr.offset_from(end_ptr) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ out-of-bounds offset_from: ALLOC has size 4, so pointer at offset 10 is out-of-bounds + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/ptr_offset_from_oob.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_unsigned_neg.rs b/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_unsigned_neg.rs new file mode 100644 index 0000000000000..06d13d9bdbaf3 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_unsigned_neg.rs @@ -0,0 +1,8 @@ +#![feature(ptr_sub_ptr)] + +fn main() { + let arr = [0u8; 8]; + let ptr1 = arr.as_ptr(); + let ptr2 = ptr1.wrapping_add(4); + let _val = unsafe { ptr1.sub_ptr(ptr2) }; //~ERROR: first pointer has smaller offset than second: 0 < 4 +} diff --git a/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_unsigned_neg.stderr b/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_unsigned_neg.stderr new file mode 100644 index 0000000000000..803aaaa55c21e --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_unsigned_neg.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: `ptr_offset_from_unsigned` called when first pointer has smaller offset than second: 0 < 4 + --> $DIR/ptr_offset_from_unsigned_neg.rs:LL:CC + | +LL | let _val = unsafe { ptr1.sub_ptr(ptr2) }; + | ^^^^^^^^^^^^^^^^^^ `ptr_offset_from_unsigned` called when first pointer has smaller offset than second: 0 < 4 + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/ptr_offset_from_unsigned_neg.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/ptr_offset_int_plus_int.rs b/src/tools/miri/tests/fail/intrinsics/ptr_offset_int_plus_int.rs new file mode 100644 index 0000000000000..19bd265c14351 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/ptr_offset_int_plus_int.rs @@ -0,0 +1,8 @@ +//@compile-flags: -Zmiri-permissive-provenance + +fn main() { + // Can't offset an integer pointer by non-zero offset. + unsafe { + let _val = (1 as *mut u8).offset(1); //~ERROR: is a dangling pointer + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/ptr_offset_int_plus_int.stderr b/src/tools/miri/tests/fail/intrinsics/ptr_offset_int_plus_int.stderr new file mode 100644 index 0000000000000..f76881011d079 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/ptr_offset_int_plus_int.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: out-of-bounds pointer arithmetic: 0x1[noalloc] is a dangling pointer (it has no provenance) + --> $DIR/ptr_offset_int_plus_int.rs:LL:CC + | +LL | let _val = (1 as *mut u8).offset(1); + | ^^^^^^^^^^^^^^^^^^^^^^^^ out-of-bounds pointer arithmetic: 0x1[noalloc] is a dangling pointer (it has no provenance) + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/ptr_offset_int_plus_int.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/ptr_offset_int_plus_ptr.rs b/src/tools/miri/tests/fail/intrinsics/ptr_offset_int_plus_ptr.rs new file mode 100644 index 0000000000000..fd3c9b44615c2 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/ptr_offset_int_plus_ptr.rs @@ -0,0 +1,9 @@ +//@compile-flags: -Zmiri-permissive-provenance + +fn main() { + let ptr = Box::into_raw(Box::new(0u32)); + // Can't start with an integer pointer and get to something usable + unsafe { + let _val = (1 as *mut u8).offset(ptr as isize); //~ERROR: is a dangling pointer + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/ptr_offset_int_plus_ptr.stderr b/src/tools/miri/tests/fail/intrinsics/ptr_offset_int_plus_ptr.stderr new file mode 100644 index 0000000000000..6e0744b7d5c39 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/ptr_offset_int_plus_ptr.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: out-of-bounds pointer arithmetic: 0x1[noalloc] is a dangling pointer (it has no provenance) + --> $DIR/ptr_offset_int_plus_ptr.rs:LL:CC + | +LL | let _val = (1 as *mut u8).offset(ptr as isize); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ out-of-bounds pointer arithmetic: 0x1[noalloc] is a dangling pointer (it has no provenance) + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/ptr_offset_int_plus_ptr.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/ptr_offset_overflow.rs b/src/tools/miri/tests/fail/intrinsics/ptr_offset_overflow.rs new file mode 100644 index 0000000000000..c3db1e23b9bfb --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/ptr_offset_overflow.rs @@ -0,0 +1,5 @@ +fn main() { + let v = [1i8, 2]; + let x = &v[1] as *const i8; + let _val = unsafe { x.offset(isize::MIN) }; //~ERROR: overflowing in-bounds pointer arithmetic +} diff --git a/src/tools/miri/tests/fail/intrinsics/ptr_offset_overflow.stderr b/src/tools/miri/tests/fail/intrinsics/ptr_offset_overflow.stderr new file mode 100644 index 0000000000000..6fb94cf5f8122 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/ptr_offset_overflow.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: overflowing in-bounds pointer arithmetic + --> $DIR/ptr_offset_overflow.rs:LL:CC + | +LL | let _val = unsafe { x.offset(isize::MIN) }; + | ^^^^^^^^^^^^^^^^^^^^ overflowing in-bounds pointer arithmetic + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/ptr_offset_overflow.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/ptr_offset_ptr_plus_0.rs b/src/tools/miri/tests/fail/intrinsics/ptr_offset_ptr_plus_0.rs new file mode 100644 index 0000000000000..575e28854b1a9 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/ptr_offset_ptr_plus_0.rs @@ -0,0 +1,7 @@ +#[rustfmt::skip] // fails with "left behind trailing whitespace" +fn main() { + let x = Box::into_raw(Box::new(0u32)); + let x = x.wrapping_offset(8); // ok, this has no inbounds tag + let _x = unsafe { x.offset(0) }; // UB despite offset 0, the pointer is not inbounds of the only object it can point to + //~^ERROR: pointer at offset 32 is out-of-bounds +} diff --git a/src/tools/miri/tests/fail/intrinsics/ptr_offset_ptr_plus_0.stderr b/src/tools/miri/tests/fail/intrinsics/ptr_offset_ptr_plus_0.stderr new file mode 100644 index 0000000000000..b18147ce379d7 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/ptr_offset_ptr_plus_0.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: out-of-bounds pointer arithmetic: ALLOC has size 4, so pointer at offset 32 is out-of-bounds + --> $DIR/ptr_offset_ptr_plus_0.rs:LL:CC + | +LL | let _x = unsafe { x.offset(0) }; // UB despite offset 0, the pointer is not inbounds of the only object it can point to + | ^^^^^^^^^^^ out-of-bounds pointer arithmetic: ALLOC has size 4, so pointer at offset 32 is out-of-bounds + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/ptr_offset_ptr_plus_0.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/raw_eq_on_ptr.rs b/src/tools/miri/tests/fail/intrinsics/raw_eq_on_ptr.rs new file mode 100644 index 0000000000000..c14f86147dbb9 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/raw_eq_on_ptr.rs @@ -0,0 +1,10 @@ +#![feature(intrinsics)] + +extern "rust-intrinsic" { + fn raw_eq(a: &T, b: &T) -> bool; +} + +fn main() { + let x = &0; + unsafe { raw_eq(&x, &x) }; //~ERROR: `raw_eq` on bytes with provenance +} diff --git a/src/tools/miri/tests/fail/intrinsics/raw_eq_on_ptr.stderr b/src/tools/miri/tests/fail/intrinsics/raw_eq_on_ptr.stderr new file mode 100644 index 0000000000000..2236ad9839c5e --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/raw_eq_on_ptr.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: `raw_eq` on bytes with provenance + --> $DIR/raw_eq_on_ptr.rs:LL:CC + | +LL | unsafe { raw_eq(&x, &x) }; + | ^^^^^^^^^^^^^^ `raw_eq` on bytes with provenance + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/raw_eq_on_ptr.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/rem-by-zero.rs b/src/tools/miri/tests/fail/intrinsics/rem-by-zero.rs new file mode 100644 index 0000000000000..ac80852e8dcfc --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/rem-by-zero.rs @@ -0,0 +1,9 @@ +#![feature(core_intrinsics)] + +use std::intrinsics::*; + +fn main() { + unsafe { + let _n = unchecked_rem(3u32, 0); //~ ERROR: calculating the remainder with a divisor of zero + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/rem-by-zero.stderr b/src/tools/miri/tests/fail/intrinsics/rem-by-zero.stderr new file mode 100644 index 0000000000000..1fc39188e5a94 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/rem-by-zero.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: calculating the remainder with a divisor of zero + --> $DIR/rem-by-zero.rs:LL:CC + | +LL | let _n = unchecked_rem(3u32, 0); + | ^^^^^^^^^^^^^^^^^^^^^^ calculating the remainder with a divisor of zero + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/rem-by-zero.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/simd-div-by-zero.rs b/src/tools/miri/tests/fail/intrinsics/simd-div-by-zero.rs new file mode 100644 index 0000000000000..5fa6f69d00593 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/simd-div-by-zero.rs @@ -0,0 +1,17 @@ +#![feature(platform_intrinsics, repr_simd)] + +extern "platform-intrinsic" { + pub(crate) fn simd_div(x: T, y: T) -> T; +} + +#[repr(simd)] +#[allow(non_camel_case_types)] +struct i32x2(i32, i32); + +fn main() { + unsafe { + let x = i32x2(1, 1); + let y = i32x2(1, 0); + simd_div(x, y); //~ERROR: Undefined Behavior: dividing by zero + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/simd-div-by-zero.stderr b/src/tools/miri/tests/fail/intrinsics/simd-div-by-zero.stderr new file mode 100644 index 0000000000000..ddab24d0c1639 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/simd-div-by-zero.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: dividing by zero + --> $DIR/simd-div-by-zero.rs:LL:CC + | +LL | simd_div(x, y); + | ^^^^^^^^^^^^^^ dividing by zero + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/simd-div-by-zero.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/simd-div-overflow.rs b/src/tools/miri/tests/fail/intrinsics/simd-div-overflow.rs new file mode 100644 index 0000000000000..57712b1b836b5 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/simd-div-overflow.rs @@ -0,0 +1,17 @@ +#![feature(platform_intrinsics, repr_simd)] + +extern "platform-intrinsic" { + pub(crate) fn simd_div(x: T, y: T) -> T; +} + +#[repr(simd)] +#[allow(non_camel_case_types)] +struct i32x2(i32, i32); + +fn main() { + unsafe { + let x = i32x2(1, i32::MIN); + let y = i32x2(1, -1); + simd_div(x, y); //~ERROR: Undefined Behavior: overflow in signed division + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/simd-div-overflow.stderr b/src/tools/miri/tests/fail/intrinsics/simd-div-overflow.stderr new file mode 100644 index 0000000000000..27d4dd9e3e73f --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/simd-div-overflow.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: overflow in signed division (dividing MIN by -1) + --> $DIR/simd-div-overflow.rs:LL:CC + | +LL | simd_div(x, y); + | ^^^^^^^^^^^^^^ overflow in signed division (dividing MIN by -1) + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/simd-div-overflow.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/simd-float-to-int.rs b/src/tools/miri/tests/fail/intrinsics/simd-float-to-int.rs new file mode 100644 index 0000000000000..a5bae36d92a46 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/simd-float-to-int.rs @@ -0,0 +1,9 @@ +//@error-pattern: cannot be represented in target type `i32` +#![feature(portable_simd)] +use std::simd::*; + +fn main() { + unsafe { + let _x: i32x2 = f32x2::from_array([f32::MAX, f32::MIN]).to_int_unchecked(); + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/simd-float-to-int.stderr b/src/tools/miri/tests/fail/intrinsics/simd-float-to-int.stderr new file mode 100644 index 0000000000000..36bb9643b48d4 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/simd-float-to-int.stderr @@ -0,0 +1,20 @@ +error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on 3.40282347E+38 which cannot be represented in target type `i32` + --> RUSTLIB/core/src/../../portable-simd/crates/core_simd/src/vector.rs:LL:CC + | +LL | unsafe { intrinsics::simd_cast(self) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on 3.40282347E+38 which cannot be represented in target type `i32` + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `std::simd::Simd::::to_int_unchecked::` at RUSTLIB/core/src/../../portable-simd/crates/core_simd/src/vector.rs:LL:CC +note: inside `main` at $DIR/simd-float-to-int.rs:LL:CC + --> $DIR/simd-float-to-int.rs:LL:CC + | +LL | let _x: i32x2 = f32x2::from_array([f32::MAX, f32::MIN]).to_int_unchecked(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/simd-gather.rs b/src/tools/miri/tests/fail/intrinsics/simd-gather.rs new file mode 100644 index 0000000000000..e394cce9a4fe3 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/simd-gather.rs @@ -0,0 +1,11 @@ +//@error-pattern: pointer to 1 byte starting at offset 9 is out-of-bounds +#![feature(portable_simd)] +use std::simd::*; + +fn main() { + unsafe { + let vec: &[i8] = &[10, 11, 12, 13, 14, 15, 16, 17, 18]; + let idxs = Simd::from_array([9, 3, 0, 17]); + let _result = Simd::gather_select_unchecked(&vec, Mask::splat(true), idxs, Simd::splat(0)); + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/simd-gather.stderr b/src/tools/miri/tests/fail/intrinsics/simd-gather.stderr new file mode 100644 index 0000000000000..29a4ef65705ab --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/simd-gather.stderr @@ -0,0 +1,20 @@ +error: Undefined Behavior: dereferencing pointer failed: ALLOC has size 9, so pointer to 1 byte starting at offset 9 is out-of-bounds + --> RUSTLIB/core/src/../../portable-simd/crates/core_simd/src/vector.rs:LL:CC + | +LL | unsafe { intrinsics::simd_gather(or, ptrs, enable.to_int()) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ dereferencing pointer failed: ALLOC has size 9, so pointer to 1 byte starting at offset 9 is out-of-bounds + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `std::simd::Simd::::gather_select_unchecked` at RUSTLIB/core/src/../../portable-simd/crates/core_simd/src/vector.rs:LL:CC +note: inside `main` at $DIR/simd-gather.rs:LL:CC + --> $DIR/simd-gather.rs:LL:CC + | +LL | let _result = Simd::gather_select_unchecked(&vec, Mask::splat(true), idxs, Simd::splat(0)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/simd-reduce-invalid-bool.rs b/src/tools/miri/tests/fail/intrinsics/simd-reduce-invalid-bool.rs new file mode 100644 index 0000000000000..354f8213120a2 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/simd-reduce-invalid-bool.rs @@ -0,0 +1,16 @@ +#![feature(platform_intrinsics, repr_simd)] + +extern "platform-intrinsic" { + pub(crate) fn simd_reduce_any(x: T) -> bool; +} + +#[repr(simd)] +#[allow(non_camel_case_types)] +struct i32x2(i32, i32); + +fn main() { + unsafe { + let x = i32x2(0, 1); + simd_reduce_any(x); //~ERROR: must be all-0-bits or all-1-bits + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/simd-reduce-invalid-bool.stderr b/src/tools/miri/tests/fail/intrinsics/simd-reduce-invalid-bool.stderr new file mode 100644 index 0000000000000..1e5ac5277e6dc --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/simd-reduce-invalid-bool.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: each element of a SIMD mask must be all-0-bits or all-1-bits + --> $DIR/simd-reduce-invalid-bool.rs:LL:CC + | +LL | simd_reduce_any(x); + | ^^^^^^^^^^^^^^^^^^ each element of a SIMD mask must be all-0-bits or all-1-bits + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/simd-reduce-invalid-bool.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/simd-rem-by-zero.rs b/src/tools/miri/tests/fail/intrinsics/simd-rem-by-zero.rs new file mode 100644 index 0000000000000..625889bb67b57 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/simd-rem-by-zero.rs @@ -0,0 +1,17 @@ +#![feature(platform_intrinsics, repr_simd)] + +extern "platform-intrinsic" { + pub(crate) fn simd_rem(x: T, y: T) -> T; +} + +#[repr(simd)] +#[allow(non_camel_case_types)] +struct i32x2(i32, i32); + +fn main() { + unsafe { + let x = i32x2(1, 1); + let y = i32x2(1, 0); + simd_rem(x, y); //~ERROR: Undefined Behavior: calculating the remainder with a divisor of zero + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/simd-rem-by-zero.stderr b/src/tools/miri/tests/fail/intrinsics/simd-rem-by-zero.stderr new file mode 100644 index 0000000000000..96248e7e599cc --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/simd-rem-by-zero.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: calculating the remainder with a divisor of zero + --> $DIR/simd-rem-by-zero.rs:LL:CC + | +LL | simd_rem(x, y); + | ^^^^^^^^^^^^^^ calculating the remainder with a divisor of zero + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/simd-rem-by-zero.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/simd-scatter.rs b/src/tools/miri/tests/fail/intrinsics/simd-scatter.rs new file mode 100644 index 0000000000000..d2bc73399548b --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/simd-scatter.rs @@ -0,0 +1,15 @@ +//@error-pattern: pointer to 1 byte starting at offset 9 is out-of-bounds +#![feature(portable_simd)] +use std::simd::*; + +fn main() { + unsafe { + let mut vec: Vec = vec![10, 11, 12, 13, 14, 15, 16, 17, 18]; + let idxs = Simd::from_array([9, 3, 0, 17]); + Simd::from_array([-27, 82, -41, 124]).scatter_select_unchecked( + &mut vec, + Mask::splat(true), + idxs, + ); + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/simd-scatter.stderr b/src/tools/miri/tests/fail/intrinsics/simd-scatter.stderr new file mode 100644 index 0000000000000..fde85a63503b6 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/simd-scatter.stderr @@ -0,0 +1,24 @@ +error: Undefined Behavior: dereferencing pointer failed: ALLOC has size 9, so pointer to 1 byte starting at offset 9 is out-of-bounds + --> RUSTLIB/core/src/../../portable-simd/crates/core_simd/src/vector.rs:LL:CC + | +LL | intrinsics::simd_scatter(self, ptrs, enable.to_int()) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ dereferencing pointer failed: ALLOC has size 9, so pointer to 1 byte starting at offset 9 is out-of-bounds + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `std::simd::Simd::::scatter_select_unchecked` at RUSTLIB/core/src/../../portable-simd/crates/core_simd/src/vector.rs:LL:CC +note: inside `main` at $DIR/simd-scatter.rs:LL:CC + --> $DIR/simd-scatter.rs:LL:CC + | +LL | / Simd::from_array([-27, 82, -41, 124]).scatter_select_unchecked( +LL | | &mut vec, +LL | | Mask::splat(true), +LL | | idxs, +LL | | ); + | |_________^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/simd-select-bitmask-invalid.rs b/src/tools/miri/tests/fail/intrinsics/simd-select-bitmask-invalid.rs new file mode 100644 index 0000000000000..8a3895ac14cf8 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/simd-select-bitmask-invalid.rs @@ -0,0 +1,17 @@ +#![feature(platform_intrinsics, repr_simd)] + +extern "platform-intrinsic" { + fn simd_select_bitmask(m: M, yes: T, no: T) -> T; +} + +#[repr(simd)] +#[allow(non_camel_case_types)] +#[derive(Copy, Clone)] +struct i32x2(i32, i32); + +fn main() { + unsafe { + let x = i32x2(0, 1); + simd_select_bitmask(0b11111111u8, x, x); //~ERROR: bitmask less than 8 bits long must be filled with 0s for the remaining bits + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/simd-select-bitmask-invalid.stderr b/src/tools/miri/tests/fail/intrinsics/simd-select-bitmask-invalid.stderr new file mode 100644 index 0000000000000..e72cce998d0ed --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/simd-select-bitmask-invalid.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: a SIMD bitmask less than 8 bits long must be filled with 0s for the remaining bits + --> $DIR/simd-select-bitmask-invalid.rs:LL:CC + | +LL | simd_select_bitmask(0b11111111u8, x, x); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ a SIMD bitmask less than 8 bits long must be filled with 0s for the remaining bits + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/simd-select-bitmask-invalid.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/simd-select-invalid-bool.rs b/src/tools/miri/tests/fail/intrinsics/simd-select-invalid-bool.rs new file mode 100644 index 0000000000000..7f7ee3af49516 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/simd-select-invalid-bool.rs @@ -0,0 +1,17 @@ +#![feature(platform_intrinsics, repr_simd)] + +extern "platform-intrinsic" { + fn simd_select(m: M, yes: T, no: T) -> T; +} + +#[repr(simd)] +#[allow(non_camel_case_types)] +#[derive(Copy, Clone)] +struct i32x2(i32, i32); + +fn main() { + unsafe { + let x = i32x2(0, 1); + simd_select(x, x, x); //~ERROR: must be all-0-bits or all-1-bits + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/simd-select-invalid-bool.stderr b/src/tools/miri/tests/fail/intrinsics/simd-select-invalid-bool.stderr new file mode 100644 index 0000000000000..277ceb54ec71e --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/simd-select-invalid-bool.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: each element of a SIMD mask must be all-0-bits or all-1-bits + --> $DIR/simd-select-invalid-bool.rs:LL:CC + | +LL | simd_select(x, x, x); + | ^^^^^^^^^^^^^^^^^^^^ each element of a SIMD mask must be all-0-bits or all-1-bits + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/simd-select-invalid-bool.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/simd-shl-too-far.rs b/src/tools/miri/tests/fail/intrinsics/simd-shl-too-far.rs new file mode 100644 index 0000000000000..5c517c17b3a4b --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/simd-shl-too-far.rs @@ -0,0 +1,17 @@ +#![feature(platform_intrinsics, repr_simd)] + +extern "platform-intrinsic" { + pub(crate) fn simd_shl(x: T, y: T) -> T; +} + +#[repr(simd)] +#[allow(non_camel_case_types)] +struct i32x2(i32, i32); + +fn main() { + unsafe { + let x = i32x2(1, 1); + let y = i32x2(100, 0); + simd_shl(x, y); //~ERROR: overflowing shift by 100 in `simd_shl` in SIMD lane 0 + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/simd-shl-too-far.stderr b/src/tools/miri/tests/fail/intrinsics/simd-shl-too-far.stderr new file mode 100644 index 0000000000000..c8445bb3cdc75 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/simd-shl-too-far.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: overflowing shift by 100 in `simd_shl` in SIMD lane 0 + --> $DIR/simd-shl-too-far.rs:LL:CC + | +LL | simd_shl(x, y); + | ^^^^^^^^^^^^^^ overflowing shift by 100 in `simd_shl` in SIMD lane 0 + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/simd-shl-too-far.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/simd-shr-too-far.rs b/src/tools/miri/tests/fail/intrinsics/simd-shr-too-far.rs new file mode 100644 index 0000000000000..5f1475a677813 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/simd-shr-too-far.rs @@ -0,0 +1,17 @@ +#![feature(platform_intrinsics, repr_simd)] + +extern "platform-intrinsic" { + pub(crate) fn simd_shr(x: T, y: T) -> T; +} + +#[repr(simd)] +#[allow(non_camel_case_types)] +struct i32x2(i32, i32); + +fn main() { + unsafe { + let x = i32x2(1, 1); + let y = i32x2(20, 40); + simd_shr(x, y); //~ERROR: overflowing shift by 40 in `simd_shr` in SIMD lane 1 + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/simd-shr-too-far.stderr b/src/tools/miri/tests/fail/intrinsics/simd-shr-too-far.stderr new file mode 100644 index 0000000000000..8eec30c5a52f2 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/simd-shr-too-far.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: overflowing shift by 40 in `simd_shr` in SIMD lane 1 + --> $DIR/simd-shr-too-far.rs:LL:CC + | +LL | simd_shr(x, y); + | ^^^^^^^^^^^^^^ overflowing shift by 40 in `simd_shr` in SIMD lane 1 + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/simd-shr-too-far.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/unchecked_add1.rs b/src/tools/miri/tests/fail/intrinsics/unchecked_add1.rs new file mode 100644 index 0000000000000..13265d0fb0ee4 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/unchecked_add1.rs @@ -0,0 +1,6 @@ +#![feature(unchecked_math)] + +fn main() { + // MAX overflow + let _val = unsafe { 40000u16.unchecked_add(30000) }; //~ ERROR: overflow executing `unchecked_add` +} diff --git a/src/tools/miri/tests/fail/intrinsics/unchecked_add1.stderr b/src/tools/miri/tests/fail/intrinsics/unchecked_add1.stderr new file mode 100644 index 0000000000000..f5e96198ee4c9 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/unchecked_add1.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: overflow executing `unchecked_add` + --> $DIR/unchecked_add1.rs:LL:CC + | +LL | let _val = unsafe { 40000u16.unchecked_add(30000) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflow executing `unchecked_add` + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/unchecked_add1.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/unchecked_add2.rs b/src/tools/miri/tests/fail/intrinsics/unchecked_add2.rs new file mode 100644 index 0000000000000..229f50321d7df --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/unchecked_add2.rs @@ -0,0 +1,6 @@ +#![feature(unchecked_math)] + +fn main() { + // MIN overflow + let _val = unsafe { (-30000i16).unchecked_add(-8000) }; //~ ERROR: overflow executing `unchecked_add` +} diff --git a/src/tools/miri/tests/fail/intrinsics/unchecked_add2.stderr b/src/tools/miri/tests/fail/intrinsics/unchecked_add2.stderr new file mode 100644 index 0000000000000..5a5c7070ae0b4 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/unchecked_add2.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: overflow executing `unchecked_add` + --> $DIR/unchecked_add2.rs:LL:CC + | +LL | let _val = unsafe { (-30000i16).unchecked_add(-8000) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflow executing `unchecked_add` + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/unchecked_add2.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/unchecked_div1.rs b/src/tools/miri/tests/fail/intrinsics/unchecked_div1.rs new file mode 100644 index 0000000000000..6706cee30ef54 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/unchecked_div1.rs @@ -0,0 +1,7 @@ +#![feature(core_intrinsics)] +fn main() { + // MIN/-1 cannot be represented + unsafe { + std::intrinsics::unchecked_div(i16::MIN, -1); //~ ERROR: overflow in signed division (dividing MIN by -1) + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/unchecked_div1.stderr b/src/tools/miri/tests/fail/intrinsics/unchecked_div1.stderr new file mode 100644 index 0000000000000..9267e0c494731 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/unchecked_div1.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: overflow in signed division (dividing MIN by -1) + --> $DIR/unchecked_div1.rs:LL:CC + | +LL | std::intrinsics::unchecked_div(i16::MIN, -1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflow in signed division (dividing MIN by -1) + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/unchecked_div1.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/unchecked_mul1.rs b/src/tools/miri/tests/fail/intrinsics/unchecked_mul1.rs new file mode 100644 index 0000000000000..810d3418dc8fe --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/unchecked_mul1.rs @@ -0,0 +1,5 @@ +#![feature(unchecked_math)] +fn main() { + // MAX overflow + let _val = unsafe { 300u16.unchecked_mul(250u16) }; //~ ERROR: overflow executing `unchecked_mul` +} diff --git a/src/tools/miri/tests/fail/intrinsics/unchecked_mul1.stderr b/src/tools/miri/tests/fail/intrinsics/unchecked_mul1.stderr new file mode 100644 index 0000000000000..9a5a585e1cce4 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/unchecked_mul1.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: overflow executing `unchecked_mul` + --> $DIR/unchecked_mul1.rs:LL:CC + | +LL | let _val = unsafe { 300u16.unchecked_mul(250u16) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflow executing `unchecked_mul` + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/unchecked_mul1.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/unchecked_mul2.rs b/src/tools/miri/tests/fail/intrinsics/unchecked_mul2.rs new file mode 100644 index 0000000000000..421019542a95a --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/unchecked_mul2.rs @@ -0,0 +1,5 @@ +#![feature(unchecked_math)] +fn main() { + // MIN overflow + let _val = unsafe { 1_000_000_000i32.unchecked_mul(-4) }; //~ ERROR: overflow executing `unchecked_mul` +} diff --git a/src/tools/miri/tests/fail/intrinsics/unchecked_mul2.stderr b/src/tools/miri/tests/fail/intrinsics/unchecked_mul2.stderr new file mode 100644 index 0000000000000..46b9f61821728 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/unchecked_mul2.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: overflow executing `unchecked_mul` + --> $DIR/unchecked_mul2.rs:LL:CC + | +LL | let _val = unsafe { 1_000_000_000i32.unchecked_mul(-4) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflow executing `unchecked_mul` + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/unchecked_mul2.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/unchecked_sub1.rs b/src/tools/miri/tests/fail/intrinsics/unchecked_sub1.rs new file mode 100644 index 0000000000000..c6e0066674413 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/unchecked_sub1.rs @@ -0,0 +1,5 @@ +#![feature(unchecked_math)] +fn main() { + // MIN overflow + let _val = unsafe { 14u32.unchecked_sub(22) }; //~ ERROR: overflow executing `unchecked_sub` +} diff --git a/src/tools/miri/tests/fail/intrinsics/unchecked_sub1.stderr b/src/tools/miri/tests/fail/intrinsics/unchecked_sub1.stderr new file mode 100644 index 0000000000000..01e569767bac0 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/unchecked_sub1.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: overflow executing `unchecked_sub` + --> $DIR/unchecked_sub1.rs:LL:CC + | +LL | let _val = unsafe { 14u32.unchecked_sub(22) }; + | ^^^^^^^^^^^^^^^^^^^^^^^ overflow executing `unchecked_sub` + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/unchecked_sub1.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/unchecked_sub2.rs b/src/tools/miri/tests/fail/intrinsics/unchecked_sub2.rs new file mode 100644 index 0000000000000..65aa292e212da --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/unchecked_sub2.rs @@ -0,0 +1,5 @@ +#![feature(unchecked_math)] +fn main() { + // MAX overflow + let _val = unsafe { 30000i16.unchecked_sub(-7000) }; //~ ERROR: overflow executing `unchecked_sub` +} diff --git a/src/tools/miri/tests/fail/intrinsics/unchecked_sub2.stderr b/src/tools/miri/tests/fail/intrinsics/unchecked_sub2.stderr new file mode 100644 index 0000000000000..38c1647b4f49f --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/unchecked_sub2.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: overflow executing `unchecked_sub` + --> $DIR/unchecked_sub2.rs:LL:CC + | +LL | let _val = unsafe { 30000i16.unchecked_sub(-7000) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflow executing `unchecked_sub` + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/unchecked_sub2.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/uninit_uninhabited_type.rs b/src/tools/miri/tests/fail/intrinsics/uninit_uninhabited_type.rs new file mode 100644 index 0000000000000..e606d8b283c8a --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/uninit_uninhabited_type.rs @@ -0,0 +1,7 @@ +#![feature(never_type)] + +#[allow(deprecated, invalid_value)] +fn main() { + unsafe { std::mem::uninitialized::() }; + //~^ ERROR: attempted to instantiate uninhabited type `!` +} diff --git a/src/tools/miri/tests/fail/intrinsics/uninit_uninhabited_type.stderr b/src/tools/miri/tests/fail/intrinsics/uninit_uninhabited_type.stderr new file mode 100644 index 0000000000000..150128ba2a41c --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/uninit_uninhabited_type.stderr @@ -0,0 +1,12 @@ +error: abnormal termination: aborted execution: attempted to instantiate uninhabited type `!` + --> $DIR/uninit_uninhabited_type.rs:LL:CC + | +LL | unsafe { std::mem::uninitialized::() }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ aborted execution: attempted to instantiate uninhabited type `!` + | + = note: inside `main` at $DIR/uninit_uninhabited_type.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/write_bytes_null.rs b/src/tools/miri/tests/fail/intrinsics/write_bytes_null.rs new file mode 100644 index 0000000000000..2f46c820fb73b --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/write_bytes_null.rs @@ -0,0 +1,10 @@ +#![feature(intrinsics)] + +// Directly call intrinsic to avoid debug assertions in libstd +extern "rust-intrinsic" { + fn write_bytes(dst: *mut T, val: u8, count: usize); +} + +fn main() { + unsafe { write_bytes::(std::ptr::null_mut(), 0, 0) }; //~ ERROR: memory access failed: null pointer is a dangling pointer +} diff --git a/src/tools/miri/tests/fail/intrinsics/write_bytes_null.stderr b/src/tools/miri/tests/fail/intrinsics/write_bytes_null.stderr new file mode 100644 index 0000000000000..b2969ca3b5929 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/write_bytes_null.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: memory access failed: null pointer is a dangling pointer (it has no provenance) + --> $DIR/write_bytes_null.rs:LL:CC + | +LL | unsafe { write_bytes::(std::ptr::null_mut(), 0, 0) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ memory access failed: null pointer is a dangling pointer (it has no provenance) + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/write_bytes_null.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/write_bytes_overflow.rs b/src/tools/miri/tests/fail/intrinsics/write_bytes_overflow.rs new file mode 100644 index 0000000000000..08bc096d6c366 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/write_bytes_overflow.rs @@ -0,0 +1,9 @@ +use std::mem; + +fn main() { + let mut y = 0; + unsafe { + (&mut y as *mut i32).write_bytes(0u8, 1usize << (mem::size_of::() * 8 - 1)); + //~^ ERROR: overflow computing total size of `write_bytes` + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/write_bytes_overflow.stderr b/src/tools/miri/tests/fail/intrinsics/write_bytes_overflow.stderr new file mode 100644 index 0000000000000..f88afde879acf --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/write_bytes_overflow.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: overflow computing total size of `write_bytes` + --> $DIR/write_bytes_overflow.rs:LL:CC + | +LL | (&mut y as *mut i32).write_bytes(0u8, 1usize << (mem::size_of::() * 8 - 1)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflow computing total size of `write_bytes` + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/write_bytes_overflow.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/zero_fn_ptr.rs b/src/tools/miri/tests/fail/intrinsics/zero_fn_ptr.rs new file mode 100644 index 0000000000000..6d9ae14c5c479 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/zero_fn_ptr.rs @@ -0,0 +1,5 @@ +#[allow(deprecated, invalid_value)] +fn main() { + unsafe { std::mem::zeroed::() }; + //~^ ERROR: attempted to zero-initialize type `fn()`, which is invalid +} diff --git a/src/tools/miri/tests/fail/intrinsics/zero_fn_ptr.stderr b/src/tools/miri/tests/fail/intrinsics/zero_fn_ptr.stderr new file mode 100644 index 0000000000000..9d44ba9f746ad --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/zero_fn_ptr.stderr @@ -0,0 +1,12 @@ +error: abnormal termination: aborted execution: attempted to zero-initialize type `fn()`, which is invalid + --> $DIR/zero_fn_ptr.rs:LL:CC + | +LL | unsafe { std::mem::zeroed::() }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ aborted execution: attempted to zero-initialize type `fn()`, which is invalid + | + = note: inside `main` at $DIR/zero_fn_ptr.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/invalid_bool.rs b/src/tools/miri/tests/fail/invalid_bool.rs new file mode 100644 index 0000000000000..525f8831c1c00 --- /dev/null +++ b/src/tools/miri/tests/fail/invalid_bool.rs @@ -0,0 +1,9 @@ +// Validation makes this fail in the wrong place +// Make sure we find these even with many checks disabled. +//@compile-flags: -Zmiri-disable-alignment-check -Zmiri-disable-stacked-borrows -Zmiri-disable-validation +#![feature(bench_black_box)] + +fn main() { + let b = unsafe { std::mem::transmute::(2) }; + let _x = b == std::hint::black_box(true); //~ ERROR: interpreting an invalid 8-bit value as a bool +} diff --git a/src/tools/miri/tests/fail/invalid_bool.stderr b/src/tools/miri/tests/fail/invalid_bool.stderr new file mode 100644 index 0000000000000..a522f6cd4fffe --- /dev/null +++ b/src/tools/miri/tests/fail/invalid_bool.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: interpreting an invalid 8-bit value as a bool: 0x02 + --> $DIR/invalid_bool.rs:LL:CC + | +LL | let _x = b == std::hint::black_box(true); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ interpreting an invalid 8-bit value as a bool: 0x02 + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/invalid_bool.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/invalid_char.rs b/src/tools/miri/tests/fail/invalid_char.rs new file mode 100644 index 0000000000000..699248229445f --- /dev/null +++ b/src/tools/miri/tests/fail/invalid_char.rs @@ -0,0 +1,10 @@ +// Validation makes this fail in the wrong place +// Make sure we find these even with many checks disabled. +//@compile-flags: -Zmiri-disable-alignment-check -Zmiri-disable-stacked-borrows -Zmiri-disable-validation + +fn main() { + let c = 0xFFFFFFu32; + assert!(std::char::from_u32(c).is_none()); + let c = unsafe { std::mem::transmute::(c) }; + let _x = c == 'x'; //~ ERROR: interpreting an invalid 32-bit value as a char +} diff --git a/src/tools/miri/tests/fail/invalid_char.stderr b/src/tools/miri/tests/fail/invalid_char.stderr new file mode 100644 index 0000000000000..d49d753d9e185 --- /dev/null +++ b/src/tools/miri/tests/fail/invalid_char.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: interpreting an invalid 32-bit value as a char: $HEX + --> $DIR/invalid_char.rs:LL:CC + | +LL | let _x = c == 'x'; + | ^^^^^^^^ interpreting an invalid 32-bit value as a char: $HEX + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/invalid_char.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/invalid_enum_tag.rs b/src/tools/miri/tests/fail/invalid_enum_tag.rs new file mode 100644 index 0000000000000..84fa2c2973901 --- /dev/null +++ b/src/tools/miri/tests/fail/invalid_enum_tag.rs @@ -0,0 +1,18 @@ +// Validation makes this fail in the wrong place +// Make sure we find these even with many checks disabled. +//@compile-flags: -Zmiri-disable-alignment-check -Zmiri-disable-stacked-borrows -Zmiri-disable-validation + +use std::mem; + +#[repr(C)] +pub enum Foo { + A, + B, + C, + D, +} + +fn main() { + let f = unsafe { std::mem::transmute::(42) }; + let _val = mem::discriminant(&f); //~ERROR: enum value has invalid tag +} diff --git a/src/tools/miri/tests/fail/invalid_enum_tag.stderr b/src/tools/miri/tests/fail/invalid_enum_tag.stderr new file mode 100644 index 0000000000000..01d931de919a4 --- /dev/null +++ b/src/tools/miri/tests/fail/invalid_enum_tag.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: enum value has invalid tag: $HEX + --> $DIR/invalid_enum_tag.rs:LL:CC + | +LL | let _val = mem::discriminant(&f); + | ^^^^^^^^^^^^^^^^^^^^^ enum value has invalid tag: $HEX + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/invalid_enum_tag.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/invalid_int.rs b/src/tools/miri/tests/fail/invalid_int.rs new file mode 100644 index 0000000000000..2435a87a6f28c --- /dev/null +++ b/src/tools/miri/tests/fail/invalid_int.rs @@ -0,0 +1,9 @@ +#![allow(invalid_value)] +// Validation makes this fail in the wrong place +// Make sure we find these even with many checks disabled. +//@compile-flags: -Zmiri-disable-alignment-check -Zmiri-disable-stacked-borrows -Zmiri-disable-validation + +fn main() { + let i = unsafe { std::mem::MaybeUninit::::uninit().assume_init() }; //~ ERROR: uninitialized + let _x = i + 0; +} diff --git a/src/tools/miri/tests/fail/invalid_int.stderr b/src/tools/miri/tests/fail/invalid_int.stderr new file mode 100644 index 0000000000000..eccdbff604574 --- /dev/null +++ b/src/tools/miri/tests/fail/invalid_int.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: using uninitialized data, but this operation requires initialized memory + --> $DIR/invalid_int.rs:LL:CC + | +LL | let i = unsafe { std::mem::MaybeUninit::::uninit().assume_init() }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ using uninitialized data, but this operation requires initialized memory + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/invalid_int.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/issue-miri-1112.rs b/src/tools/miri/tests/fail/issue-miri-1112.rs new file mode 100644 index 0000000000000..387253a3f9872 --- /dev/null +++ b/src/tools/miri/tests/fail/issue-miri-1112.rs @@ -0,0 +1,42 @@ +trait Empty {} + +#[repr(transparent)] +pub struct FunnyPointer(dyn Empty); + +#[repr(C)] +pub struct Meta { + drop_fn: fn(&mut ()), + size: usize, + align: usize, +} + +impl Meta { + pub fn new() -> Self { + Meta { drop_fn: |_| {}, size: 0, align: 1 } + } +} + +#[repr(C)] +pub struct FatPointer { + pub data: *const (), + pub vtable: *const (), +} + +impl FunnyPointer { + pub unsafe fn from_data_ptr(data: &String, ptr: *const Meta) -> &Self { + let obj = FatPointer { + data: data as *const _ as *const (), + vtable: ptr as *const _ as *const (), + }; + let obj = std::mem::transmute::(obj); //~ ERROR: expected a vtable pointer + &*obj + } +} + +fn main() { + unsafe { + let meta = Meta::new(); + let hello = "hello".to_string(); + let _raw: &FunnyPointer = FunnyPointer::from_data_ptr(&hello, &meta as *const _); + } +} diff --git a/src/tools/miri/tests/fail/issue-miri-1112.stderr b/src/tools/miri/tests/fail/issue-miri-1112.stderr new file mode 100644 index 0000000000000..e6644a72849ff --- /dev/null +++ b/src/tools/miri/tests/fail/issue-miri-1112.stderr @@ -0,0 +1,20 @@ +error: Undefined Behavior: constructing invalid value: encountered $HEX[ALLOC], but expected a vtable pointer + --> $DIR/issue-miri-1112.rs:LL:CC + | +LL | let obj = std::mem::transmute::(obj); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered $HEX[ALLOC], but expected a vtable pointer + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `FunnyPointer::from_data_ptr` at $DIR/issue-miri-1112.rs:LL:CC +note: inside `main` at $DIR/issue-miri-1112.rs:LL:CC + --> $DIR/issue-miri-1112.rs:LL:CC + | +LL | let _raw: &FunnyPointer = FunnyPointer::from_data_ptr(&hello, &meta as *const _); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/issue-miri-2432.rs b/src/tools/miri/tests/fail/issue-miri-2432.rs new file mode 100644 index 0000000000000..f822479c43685 --- /dev/null +++ b/src/tools/miri/tests/fail/issue-miri-2432.rs @@ -0,0 +1,19 @@ +#![allow(where_clauses_object_safety)] + +trait Trait {} + +trait X { + fn foo(&self) + where + Self: Trait; +} + +impl X for () { + fn foo(&self) {} +} + +impl Trait for dyn X {} + +pub fn main() { + ::foo(&()); //~ERROR: trying to call something that is not a method +} diff --git a/src/tools/miri/tests/fail/issue-miri-2432.stderr b/src/tools/miri/tests/fail/issue-miri-2432.stderr new file mode 100644 index 0000000000000..b8e13b61ceb60 --- /dev/null +++ b/src/tools/miri/tests/fail/issue-miri-2432.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: `dyn` call trying to call something that is not a method + --> $DIR/issue-miri-2432.rs:LL:CC + | +LL | ::foo(&()); + | ^^^^^^^^^^^^^^^^^^^^^^ `dyn` call trying to call something that is not a method + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/issue-miri-2432.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/memleak.rs b/src/tools/miri/tests/fail/memleak.rs new file mode 100644 index 0000000000000..d384caf81a57e --- /dev/null +++ b/src/tools/miri/tests/fail/memleak.rs @@ -0,0 +1,6 @@ +//@error-pattern: the evaluated program leaked memory +//@normalize-stderr-test: ".*│.*" -> "$$stripped$$" + +fn main() { + std::mem::forget(Box::new(42)); +} diff --git a/src/tools/miri/tests/fail/memleak.stderr b/src/tools/miri/tests/fail/memleak.stderr new file mode 100644 index 0000000000000..f8b62af3eb857 --- /dev/null +++ b/src/tools/miri/tests/fail/memleak.stderr @@ -0,0 +1,10 @@ +The following memory was leaked: ALLOC (Rust heap, size: 4, align: 4) { +$stripped$ +} + +error: the evaluated program leaked memory + +note: pass `-Zmiri-ignore-leaks` to disable this check + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/memleak_rc.32bit.stderr b/src/tools/miri/tests/fail/memleak_rc.32bit.stderr new file mode 100644 index 0000000000000..da222609091ab --- /dev/null +++ b/src/tools/miri/tests/fail/memleak_rc.32bit.stderr @@ -0,0 +1,10 @@ +The following memory was leaked: ALLOC (Rust heap, size: 16, align: 4) { +$stripped$ +} + +error: the evaluated program leaked memory + +note: pass `-Zmiri-ignore-leaks` to disable this check + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/memleak_rc.64bit.stderr b/src/tools/miri/tests/fail/memleak_rc.64bit.stderr new file mode 100644 index 0000000000000..8c24bbc779bd6 --- /dev/null +++ b/src/tools/miri/tests/fail/memleak_rc.64bit.stderr @@ -0,0 +1,11 @@ +The following memory was leaked: ALLOC (Rust heap, size: 32, align: 8) { +$stripped$ +$stripped$ +} + +error: the evaluated program leaked memory + +note: pass `-Zmiri-ignore-leaks` to disable this check + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/memleak_rc.rs b/src/tools/miri/tests/fail/memleak_rc.rs new file mode 100644 index 0000000000000..76ecd71b011aa --- /dev/null +++ b/src/tools/miri/tests/fail/memleak_rc.rs @@ -0,0 +1,14 @@ +//@error-pattern: the evaluated program leaked memory +//@stderr-per-bitwidth +//@normalize-stderr-test: ".*│.*" -> "$$stripped$$" + +use std::cell::RefCell; +use std::rc::Rc; + +struct Dummy(Rc>>); + +fn main() { + let x = Dummy(Rc::new(RefCell::new(None))); + let y = Dummy(x.0.clone()); + *x.0.borrow_mut() = Some(y); +} diff --git a/src/tools/miri/tests/fail/memleak_rc.stderr b/src/tools/miri/tests/fail/memleak_rc.stderr new file mode 100644 index 0000000000000..290de49c82c0b --- /dev/null +++ b/src/tools/miri/tests/fail/memleak_rc.stderr @@ -0,0 +1,11 @@ +The following memory was leaked: ALLOC (Rust heap, size: 32, align: 8) { + 0x00 │ 01 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 │ ................ + 0x10 │ 00 00 00 00 00 00 00 00 ╾$HEX[a1765]─╼ │ ........╾──────╼ +} + +error: the evaluated program leaked memory + +note: pass `-Zmiri-ignore-leaks` to disable this check + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/modifying_constants.rs b/src/tools/miri/tests/fail/modifying_constants.rs new file mode 100644 index 0000000000000..2783ebd155ff5 --- /dev/null +++ b/src/tools/miri/tests/fail/modifying_constants.rs @@ -0,0 +1,9 @@ +// This should fail even without validation/SB +//@compile-flags: -Zmiri-disable-validation -Zmiri-disable-stacked-borrows + +fn main() { + let x = &1; // the `&1` is promoted to a constant, but it used to be that only the pointer is marked static, not the pointee + let y = unsafe { &mut *(x as *const i32 as *mut i32) }; + *y = 42; //~ ERROR: read-only + assert_eq!(*x, 42); +} diff --git a/src/tools/miri/tests/fail/modifying_constants.stderr b/src/tools/miri/tests/fail/modifying_constants.stderr new file mode 100644 index 0000000000000..6425a5d7a0ad4 --- /dev/null +++ b/src/tools/miri/tests/fail/modifying_constants.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: writing to ALLOC which is read-only + --> $DIR/modifying_constants.rs:LL:CC + | +LL | *y = 42; + | ^^^^^^^ writing to ALLOC which is read-only + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/modifying_constants.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/never_say_never.rs b/src/tools/miri/tests/fail/never_say_never.rs new file mode 100644 index 0000000000000..f6d3dc790bf00 --- /dev/null +++ b/src/tools/miri/tests/fail/never_say_never.rs @@ -0,0 +1,17 @@ +// This should fail even without validation +//@compile-flags: -Zmiri-disable-validation + +#![feature(never_type)] +#![allow(unreachable_code)] + +fn main() { + let y = &5; + let x: ! = unsafe { + *(y as *const _ as *const !) //~ ERROR: entering unreachable code + }; + f(x) +} + +fn f(x: !) -> ! { + x +} diff --git a/src/tools/miri/tests/fail/never_say_never.stderr b/src/tools/miri/tests/fail/never_say_never.stderr new file mode 100644 index 0000000000000..a2a63b8baf594 --- /dev/null +++ b/src/tools/miri/tests/fail/never_say_never.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: entering unreachable code + --> $DIR/never_say_never.rs:LL:CC + | +LL | *(y as *const _ as *const !) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ entering unreachable code + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/never_say_never.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/never_transmute_humans.rs b/src/tools/miri/tests/fail/never_transmute_humans.rs new file mode 100644 index 0000000000000..de723433dc283 --- /dev/null +++ b/src/tools/miri/tests/fail/never_transmute_humans.rs @@ -0,0 +1,12 @@ +// This should fail even without validation +//@compile-flags: -Zmiri-disable-validation + +#![feature(never_type)] + +struct Human; + +fn main() { + let _x: ! = unsafe { + std::mem::transmute::(Human) //~ ERROR: transmuting to uninhabited + }; +} diff --git a/src/tools/miri/tests/fail/never_transmute_humans.stderr b/src/tools/miri/tests/fail/never_transmute_humans.stderr new file mode 100644 index 0000000000000..e8df4739f9bcb --- /dev/null +++ b/src/tools/miri/tests/fail/never_transmute_humans.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: transmuting to uninhabited type + --> $DIR/never_transmute_humans.rs:LL:CC + | +LL | std::mem::transmute::(Human) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ transmuting to uninhabited type + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/never_transmute_humans.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/never_transmute_void.rs b/src/tools/miri/tests/fail/never_transmute_void.rs new file mode 100644 index 0000000000000..19473e9ac2141 --- /dev/null +++ b/src/tools/miri/tests/fail/never_transmute_void.rs @@ -0,0 +1,20 @@ +// This should fail even without validation +//@compile-flags: -Zmiri-disable-validation +//@require-annotations-for-level: ERROR + +#![feature(never_type)] +#![allow(unused, invalid_value)] + +mod m { + enum VoidI {} + pub struct Void(VoidI); + + pub fn f(v: Void) -> ! { + match v.0 {} //~ ERROR: entering unreachable code + } +} + +fn main() { + let v = unsafe { std::mem::transmute::<(), m::Void>(()) }; + m::f(v); //~ NOTE: inside `main` +} diff --git a/src/tools/miri/tests/fail/never_transmute_void.stderr b/src/tools/miri/tests/fail/never_transmute_void.stderr new file mode 100644 index 0000000000000..4c3a3d075f028 --- /dev/null +++ b/src/tools/miri/tests/fail/never_transmute_void.stderr @@ -0,0 +1,20 @@ +error: Undefined Behavior: entering unreachable code + --> $DIR/never_transmute_void.rs:LL:CC + | +LL | match v.0 {} + | ^^^ entering unreachable code + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `m::f` at $DIR/never_transmute_void.rs:LL:CC +note: inside `main` at $DIR/never_transmute_void.rs:LL:CC + --> $DIR/never_transmute_void.rs:LL:CC + | +LL | m::f(v); + | ^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/no_main.rs b/src/tools/miri/tests/fail/no_main.rs new file mode 100644 index 0000000000000..e28205040871a --- /dev/null +++ b/src/tools/miri/tests/fail/no_main.rs @@ -0,0 +1,2 @@ +//@error-pattern: miri can only run programs that have a main function +#![no_main] diff --git a/src/tools/miri/tests/fail/no_main.stderr b/src/tools/miri/tests/fail/no_main.stderr new file mode 100644 index 0000000000000..88bdfb4e387cf --- /dev/null +++ b/src/tools/miri/tests/fail/no_main.stderr @@ -0,0 +1,4 @@ +error: miri can only run programs that have a main function + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/panic/bad_miri_start_panic.rs b/src/tools/miri/tests/fail/panic/bad_miri_start_panic.rs new file mode 100644 index 0000000000000..4b0ae60b10101 --- /dev/null +++ b/src/tools/miri/tests/fail/panic/bad_miri_start_panic.rs @@ -0,0 +1,12 @@ +//@compile-flags: -Zmiri-disable-abi-check +// This feature is required to trigger the error using the "C" ABI. +#![feature(c_unwind)] + +extern "C" { + fn miri_start_panic(payload: *mut u8) -> !; +} + +fn main() { + unsafe { miri_start_panic(&mut 0) } + //~^ ERROR: unwinding past a stack frame that does not allow unwinding +} diff --git a/src/tools/miri/tests/fail/panic/bad_miri_start_panic.stderr b/src/tools/miri/tests/fail/panic/bad_miri_start_panic.stderr new file mode 100644 index 0000000000000..3bd2be03ea1ff --- /dev/null +++ b/src/tools/miri/tests/fail/panic/bad_miri_start_panic.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: unwinding past a stack frame that does not allow unwinding + --> $DIR/bad_miri_start_panic.rs:LL:CC + | +LL | unsafe { miri_start_panic(&mut 0) } + | ^^^^^^^^^^^^^^^^^^^^^^^^ unwinding past a stack frame that does not allow unwinding + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/bad_miri_start_panic.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/panic/bad_unwind.rs b/src/tools/miri/tests/fail/panic/bad_unwind.rs new file mode 100644 index 0000000000000..370b372a7d373 --- /dev/null +++ b/src/tools/miri/tests/fail/panic/bad_unwind.rs @@ -0,0 +1,14 @@ +#![feature(c_unwind)] + +//! Unwinding when the caller ABI is "C" (without "-unwind") is UB. + +extern "C-unwind" fn unwind() { + panic!(); +} + +fn main() { + let unwind: extern "C-unwind" fn() = unwind; + let unwind: extern "C" fn() = unsafe { std::mem::transmute(unwind) }; + std::panic::catch_unwind(|| unwind()).unwrap_err(); + //~^ ERROR: unwinding past a stack frame that does not allow unwinding +} diff --git a/src/tools/miri/tests/fail/panic/bad_unwind.stderr b/src/tools/miri/tests/fail/panic/bad_unwind.stderr new file mode 100644 index 0000000000000..23c33f5e7f3ff --- /dev/null +++ b/src/tools/miri/tests/fail/panic/bad_unwind.stderr @@ -0,0 +1,25 @@ +thread 'main' panicked at 'explicit panic', $DIR/bad_unwind.rs:LL:CC +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +error: Undefined Behavior: unwinding past a stack frame that does not allow unwinding + --> $DIR/bad_unwind.rs:LL:CC + | +LL | std::panic::catch_unwind(|| unwind()).unwrap_err(); + | ^^^^^^^^ unwinding past a stack frame that does not allow unwinding + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside closure at $DIR/bad_unwind.rs:LL:CC + = note: inside `std::panicking::r#try::do_call::<[closure@$DIR/bad_unwind.rs:LL:CC], ()>` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panicking::r#try::<(), [closure@$DIR/bad_unwind.rs:LL:CC]>` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panic::catch_unwind::<[closure@$DIR/bad_unwind.rs:LL:CC], ()>` at RUSTLIB/std/src/panic.rs:LL:CC +note: inside `main` at $DIR/bad_unwind.rs:LL:CC + --> $DIR/bad_unwind.rs:LL:CC + | +LL | std::panic::catch_unwind(|| unwind()).unwrap_err(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/panic/double_panic.rs b/src/tools/miri/tests/fail/panic/double_panic.rs new file mode 100644 index 0000000000000..8919d51bb2f74 --- /dev/null +++ b/src/tools/miri/tests/fail/panic/double_panic.rs @@ -0,0 +1,14 @@ +//@error-pattern: the program aborted +//@normalize-stderr-test: "\| +\^+" -> "| ^" +//@normalize-stderr-test: "unsafe \{ libc::abort\(\) \}|crate::intrinsics::abort\(\);" -> "ABORT();" + +struct Foo; +impl Drop for Foo { + fn drop(&mut self) { + panic!("second"); + } +} +fn main() { + let _foo = Foo; + panic!("first"); +} diff --git a/src/tools/miri/tests/fail/panic/double_panic.stderr b/src/tools/miri/tests/fail/panic/double_panic.stderr new file mode 100644 index 0000000000000..f1d2b4de97cc2 --- /dev/null +++ b/src/tools/miri/tests/fail/panic/double_panic.stderr @@ -0,0 +1,94 @@ +thread 'main' panicked at 'first', $DIR/double_panic.rs:LL:CC +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +thread 'main' panicked at 'second', $DIR/double_panic.rs:LL:CC +stack backtrace: + 0: std::backtrace_rs::backtrace::miri::trace_unsynchronized + at RUSTLIB/std/src/../../backtrace/src/backtrace/miri.rs:LL:CC + 1: std::backtrace_rs::backtrace::miri::trace + at RUSTLIB/std/src/../../backtrace/src/backtrace/miri.rs:LL:CC + 2: std::backtrace_rs::backtrace::trace_unsynchronized + at RUSTLIB/std/src/../../backtrace/src/backtrace/mod.rs:LL:CC + 3: std::sys_common::backtrace::_print_fmt + at RUSTLIB/std/src/sys_common/backtrace.rs:LL:CC + 4: ::fmt + at RUSTLIB/std/src/sys_common/backtrace.rs:LL:CC + 5: std::fmt::write + at RUSTLIB/core/src/fmt/mod.rs:LL:CC + 6: ::write_fmt + at RUSTLIB/std/src/io/mod.rs:LL:CC + 7: std::sys_common::backtrace::_print + at RUSTLIB/std/src/sys_common/backtrace.rs:LL:CC + 8: std::sys_common::backtrace::print + at RUSTLIB/std/src/sys_common/backtrace.rs:LL:CC + 9: std::panicking::default_hook::{closure#1} + at RUSTLIB/std/src/panicking.rs:LL:CC + 10: std::panicking::default_hook + at RUSTLIB/std/src/panicking.rs:LL:CC + 11: std::panicking::rust_panic_with_hook + at RUSTLIB/std/src/panicking.rs:LL:CC + 12: std::rt::begin_panic::{closure#0} + at RUSTLIB/std/src/panicking.rs:LL:CC + 13: std::sys_common::backtrace::__rust_end_short_backtrace + at RUSTLIB/std/src/sys_common/backtrace.rs:LL:CC + 14: std::rt::begin_panic + at RUSTLIB/std/src/panicking.rs:LL:CC + 15: ::drop + at $DIR/double_panic.rs:LL:CC + 16: std::ptr::drop_in_place - shim(Some(Foo)) + at RUSTLIB/core/src/ptr/mod.rs:LL:CC + 17: main + at $DIR/double_panic.rs:LL:CC + 18: >::call_once - shim(fn()) + at RUSTLIB/core/src/ops/function.rs:LL:CC + 19: std::sys_common::backtrace::__rust_begin_short_backtrace + at RUSTLIB/std/src/sys_common/backtrace.rs:LL:CC + 20: std::rt::lang_start::{closure#0} + at RUSTLIB/std/src/rt.rs:LL:CC + 21: std::ops::function::impls::call_once + at RUSTLIB/core/src/ops/function.rs:LL:CC + 22: std::panicking::r#try::do_call + at RUSTLIB/std/src/panicking.rs:LL:CC + 23: std::panicking::r#try + at RUSTLIB/std/src/panicking.rs:LL:CC + 24: std::panic::catch_unwind + at RUSTLIB/std/src/panic.rs:LL:CC + 25: std::rt::lang_start_internal::{closure#2} + at RUSTLIB/std/src/rt.rs:LL:CC + 26: std::panicking::r#try::do_call + at RUSTLIB/std/src/panicking.rs:LL:CC + 27: std::panicking::r#try + at RUSTLIB/std/src/panicking.rs:LL:CC + 28: std::panic::catch_unwind + at RUSTLIB/std/src/panic.rs:LL:CC + 29: std::rt::lang_start_internal + at RUSTLIB/std/src/rt.rs:LL:CC + 30: std::rt::lang_start + at RUSTLIB/std/src/rt.rs:LL:CC +thread panicked while panicking. aborting. +error: abnormal termination: the program aborted execution + --> RUSTLIB/std/src/sys/PLATFORM/mod.rs:LL:CC + | +LL | ABORT(); + | ^ the program aborted execution + | + = note: inside `std::sys::PLATFORM::abort_internal` at RUSTLIB/std/src/sys/PLATFORM/mod.rs:LL:CC + = note: inside `std::panicking::rust_panic_with_hook` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::sys_common::backtrace::__rust_end_short_backtrace::<[closure@std::rt::begin_panic<&str>::{closure#0}], !>` at RUSTLIB/std/src/sys_common/backtrace.rs:LL:CC +note: inside `::drop` at RUSTLIB/std/src/panic.rs:LL:CC + --> $DIR/double_panic.rs:LL:CC + | +LL | panic!("second"); + | ^ + = note: inside `std::ptr::drop_in_place:: - shim(Some(Foo))` at RUSTLIB/core/src/ptr/mod.rs:LL:CC +note: inside `main` at $DIR/double_panic.rs:LL:CC + --> $DIR/double_panic.rs:LL:CC + | +LL | } + | ^ + = note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info) + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/panic/panic_abort1.rs b/src/tools/miri/tests/fail/panic/panic_abort1.rs new file mode 100644 index 0000000000000..00a01ce6e8137 --- /dev/null +++ b/src/tools/miri/tests/fail/panic/panic_abort1.rs @@ -0,0 +1,8 @@ +//@error-pattern: the program aborted execution +//@normalize-stderr-test: "\| +\^+" -> "| ^" +//@normalize-stderr-test: "libc::abort\(\);|core::intrinsics::abort\(\);" -> "ABORT();" +//@compile-flags: -C panic=abort + +fn main() { + std::panic!("panicking from libstd"); +} diff --git a/src/tools/miri/tests/fail/panic/panic_abort1.stderr b/src/tools/miri/tests/fail/panic/panic_abort1.stderr new file mode 100644 index 0000000000000..7547199454643 --- /dev/null +++ b/src/tools/miri/tests/fail/panic/panic_abort1.stderr @@ -0,0 +1,25 @@ +thread 'main' panicked at 'panicking from libstd', $DIR/panic_abort1.rs:LL:CC +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +error: abnormal termination: the program aborted execution + --> RUSTLIB/panic_abort/src/lib.rs:LL:CC + | +LL | ABORT(); + | ^ the program aborted execution + | + = note: inside `panic_abort::__rust_start_panic::abort` at RUSTLIB/panic_abort/src/lib.rs:LL:CC + = note: inside `panic_abort::__rust_start_panic` at RUSTLIB/panic_abort/src/lib.rs:LL:CC + = note: inside `std::panicking::rust_panic` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panicking::rust_panic_with_hook` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::sys_common::backtrace::__rust_end_short_backtrace::<[closure@std::rt::begin_panic<&str>::{closure#0}], !>` at RUSTLIB/std/src/sys_common/backtrace.rs:LL:CC +note: inside `main` at RUSTLIB/std/src/panic.rs:LL:CC + --> $DIR/panic_abort1.rs:LL:CC + | +LL | std::panic!("panicking from libstd"); + | ^ + = note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `std::panic` (in Nightly builds, run with -Z macro-backtrace for more info) + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/panic/panic_abort2.rs b/src/tools/miri/tests/fail/panic/panic_abort2.rs new file mode 100644 index 0000000000000..dee0de96703a1 --- /dev/null +++ b/src/tools/miri/tests/fail/panic/panic_abort2.rs @@ -0,0 +1,8 @@ +//@error-pattern: the program aborted execution +//@normalize-stderr-test: "\| +\^+" -> "| ^" +//@normalize-stderr-test: "libc::abort\(\);|core::intrinsics::abort\(\);" -> "ABORT();" +//@compile-flags: -C panic=abort + +fn main() { + std::panic!("{}-panicking from libstd", 42); +} diff --git a/src/tools/miri/tests/fail/panic/panic_abort2.stderr b/src/tools/miri/tests/fail/panic/panic_abort2.stderr new file mode 100644 index 0000000000000..2fdf889d798a2 --- /dev/null +++ b/src/tools/miri/tests/fail/panic/panic_abort2.stderr @@ -0,0 +1,26 @@ +thread 'main' panicked at '42-panicking from libstd', $DIR/panic_abort2.rs:LL:CC +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +error: abnormal termination: the program aborted execution + --> RUSTLIB/panic_abort/src/lib.rs:LL:CC + | +LL | ABORT(); + | ^ the program aborted execution + | + = note: inside `panic_abort::__rust_start_panic::abort` at RUSTLIB/panic_abort/src/lib.rs:LL:CC + = note: inside `panic_abort::__rust_start_panic` at RUSTLIB/panic_abort/src/lib.rs:LL:CC + = note: inside `std::panicking::rust_panic` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panicking::rust_panic_with_hook` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::sys_common::backtrace::__rust_end_short_backtrace::<[closure@std::panicking::begin_panic_handler::{closure#0}], !>` at RUSTLIB/std/src/sys_common/backtrace.rs:LL:CC + = note: inside `std::panicking::begin_panic_handler` at RUSTLIB/std/src/panicking.rs:LL:CC +note: inside `main` at RUSTLIB/std/src/panic.rs:LL:CC + --> $DIR/panic_abort2.rs:LL:CC + | +LL | std::panic!("{}-panicking from libstd", 42); + | ^ + = note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `std::panic` (in Nightly builds, run with -Z macro-backtrace for more info) + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/panic/panic_abort3.rs b/src/tools/miri/tests/fail/panic/panic_abort3.rs new file mode 100644 index 0000000000000..a448aab3ea458 --- /dev/null +++ b/src/tools/miri/tests/fail/panic/panic_abort3.rs @@ -0,0 +1,8 @@ +//@error-pattern: the program aborted execution +//@normalize-stderr-test: "\| +\^+" -> "| ^" +//@normalize-stderr-test: "libc::abort\(\);|core::intrinsics::abort\(\);" -> "ABORT();" +//@compile-flags: -C panic=abort + +fn main() { + core::panic!("panicking from libcore"); +} diff --git a/src/tools/miri/tests/fail/panic/panic_abort3.stderr b/src/tools/miri/tests/fail/panic/panic_abort3.stderr new file mode 100644 index 0000000000000..8704b0d940b7a --- /dev/null +++ b/src/tools/miri/tests/fail/panic/panic_abort3.stderr @@ -0,0 +1,26 @@ +thread 'main' panicked at 'panicking from libcore', $DIR/panic_abort3.rs:LL:CC +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +error: abnormal termination: the program aborted execution + --> RUSTLIB/panic_abort/src/lib.rs:LL:CC + | +LL | ABORT(); + | ^ the program aborted execution + | + = note: inside `panic_abort::__rust_start_panic::abort` at RUSTLIB/panic_abort/src/lib.rs:LL:CC + = note: inside `panic_abort::__rust_start_panic` at RUSTLIB/panic_abort/src/lib.rs:LL:CC + = note: inside `std::panicking::rust_panic` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panicking::rust_panic_with_hook` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::sys_common::backtrace::__rust_end_short_backtrace::<[closure@std::panicking::begin_panic_handler::{closure#0}], !>` at RUSTLIB/std/src/sys_common/backtrace.rs:LL:CC + = note: inside `std::panicking::begin_panic_handler` at RUSTLIB/std/src/panicking.rs:LL:CC +note: inside `main` at RUSTLIB/core/src/panic.rs:LL:CC + --> $DIR/panic_abort3.rs:LL:CC + | +LL | core::panic!("panicking from libcore"); + | ^ + = note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `core::panic` (in Nightly builds, run with -Z macro-backtrace for more info) + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/panic/panic_abort4.rs b/src/tools/miri/tests/fail/panic/panic_abort4.rs new file mode 100644 index 0000000000000..4995dad9d71ab --- /dev/null +++ b/src/tools/miri/tests/fail/panic/panic_abort4.rs @@ -0,0 +1,8 @@ +//@error-pattern: the program aborted execution +//@normalize-stderr-test: "\| +\^+" -> "| ^" +//@normalize-stderr-test: "libc::abort\(\);|core::intrinsics::abort\(\);" -> "ABORT();" +//@compile-flags: -C panic=abort + +fn main() { + core::panic!("{}-panicking from libcore", 42); +} diff --git a/src/tools/miri/tests/fail/panic/panic_abort4.stderr b/src/tools/miri/tests/fail/panic/panic_abort4.stderr new file mode 100644 index 0000000000000..1d75d72c0317c --- /dev/null +++ b/src/tools/miri/tests/fail/panic/panic_abort4.stderr @@ -0,0 +1,26 @@ +thread 'main' panicked at '42-panicking from libcore', $DIR/panic_abort4.rs:LL:CC +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +error: abnormal termination: the program aborted execution + --> RUSTLIB/panic_abort/src/lib.rs:LL:CC + | +LL | ABORT(); + | ^ the program aborted execution + | + = note: inside `panic_abort::__rust_start_panic::abort` at RUSTLIB/panic_abort/src/lib.rs:LL:CC + = note: inside `panic_abort::__rust_start_panic` at RUSTLIB/panic_abort/src/lib.rs:LL:CC + = note: inside `std::panicking::rust_panic` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panicking::rust_panic_with_hook` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::sys_common::backtrace::__rust_end_short_backtrace::<[closure@std::panicking::begin_panic_handler::{closure#0}], !>` at RUSTLIB/std/src/sys_common/backtrace.rs:LL:CC + = note: inside `std::panicking::begin_panic_handler` at RUSTLIB/std/src/panicking.rs:LL:CC +note: inside `main` at RUSTLIB/core/src/panic.rs:LL:CC + --> $DIR/panic_abort4.rs:LL:CC + | +LL | core::panic!("{}-panicking from libcore", 42); + | ^ + = note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `core::panic` (in Nightly builds, run with -Z macro-backtrace for more info) + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/panic/unwind_panic_abort.rs b/src/tools/miri/tests/fail/panic/unwind_panic_abort.rs new file mode 100644 index 0000000000000..c21fa85a90439 --- /dev/null +++ b/src/tools/miri/tests/fail/panic/unwind_panic_abort.rs @@ -0,0 +1,13 @@ +//@compile-flags: -Cpanic=abort + +//! Unwinding despite `-C panic=abort` is an error. + +extern "Rust" { + fn miri_start_panic(payload: *mut u8) -> !; +} + +fn main() { + unsafe { + miri_start_panic(&mut 0); //~ ERROR: unwinding past a stack frame that does not allow unwinding + } +} diff --git a/src/tools/miri/tests/fail/panic/unwind_panic_abort.stderr b/src/tools/miri/tests/fail/panic/unwind_panic_abort.stderr new file mode 100644 index 0000000000000..363e69ba41db9 --- /dev/null +++ b/src/tools/miri/tests/fail/panic/unwind_panic_abort.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: unwinding past a stack frame that does not allow unwinding + --> $DIR/unwind_panic_abort.rs:LL:CC + | +LL | miri_start_panic(&mut 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^ unwinding past a stack frame that does not allow unwinding + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/unwind_panic_abort.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/pointer_partial_overwrite.rs b/src/tools/miri/tests/fail/pointer_partial_overwrite.rs new file mode 100644 index 0000000000000..63f0649b8ed3e --- /dev/null +++ b/src/tools/miri/tests/fail/pointer_partial_overwrite.rs @@ -0,0 +1,17 @@ +// Make sure we find these even with many checks disabled. +//@compile-flags: -Zmiri-disable-alignment-check -Zmiri-disable-stacked-borrows -Zmiri-disable-validation + +// Test what happens when we overwrite parts of a pointer. +// Also see . + +fn main() { + let mut p = &42; + unsafe { + let ptr: *mut _ = &mut p; + *(ptr as *mut u8) = 123; // if we ever support 8 bit pointers, this is gonna cause + // "attempted to interpret some raw bytes as a pointer address" instead of + // "attempted to read undefined bytes" + } + let x = *p; //~ ERROR: this operation requires initialized memory + panic!("this should never print: {}", x); +} diff --git a/src/tools/miri/tests/fail/pointer_partial_overwrite.stderr b/src/tools/miri/tests/fail/pointer_partial_overwrite.stderr new file mode 100644 index 0000000000000..7d10b75e8805f --- /dev/null +++ b/src/tools/miri/tests/fail/pointer_partial_overwrite.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: using uninitialized data, but this operation requires initialized memory + --> $DIR/pointer_partial_overwrite.rs:LL:CC + | +LL | let x = *p; + | ^^ using uninitialized data, but this operation requires initialized memory + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/pointer_partial_overwrite.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/provenance/provenance_transmute.rs b/src/tools/miri/tests/fail/provenance/provenance_transmute.rs new file mode 100644 index 0000000000000..abcfc060e52bc --- /dev/null +++ b/src/tools/miri/tests/fail/provenance/provenance_transmute.rs @@ -0,0 +1,27 @@ +//@compile-flags: -Zmiri-permissive-provenance +#![feature(strict_provenance)] + +use std::mem; + +// This is the example from +// . + +unsafe fn deref(left: *const u8, right: *const u8) { + let left_int: usize = mem::transmute(left); + let right_int: usize = mem::transmute(right); + if left_int == right_int { + // The compiler is allowed to replace `left_int` by `right_int` here... + let left_ptr: *const u8 = mem::transmute(left_int); + // ...which however means here it could be dereferencing the wrong pointer. + let _val = *left_ptr; //~ERROR: dereferencing pointer failed + } +} + +fn main() { + let ptr1 = &0u8 as *const u8; + let ptr2 = &1u8 as *const u8; + unsafe { + // Two pointers with the same address but different provenance. + deref(ptr1, ptr2.with_addr(ptr1.addr())); + } +} diff --git a/src/tools/miri/tests/fail/provenance/provenance_transmute.stderr b/src/tools/miri/tests/fail/provenance/provenance_transmute.stderr new file mode 100644 index 0000000000000..f7c5f6046e198 --- /dev/null +++ b/src/tools/miri/tests/fail/provenance/provenance_transmute.stderr @@ -0,0 +1,20 @@ +error: Undefined Behavior: dereferencing pointer failed: $HEX[noalloc] is a dangling pointer (it has no provenance) + --> $DIR/provenance_transmute.rs:LL:CC + | +LL | let _val = *left_ptr; + | ^^^^^^^^^ dereferencing pointer failed: $HEX[noalloc] is a dangling pointer (it has no provenance) + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `deref` at $DIR/provenance_transmute.rs:LL:CC +note: inside `main` at $DIR/provenance_transmute.rs:LL:CC + --> $DIR/provenance_transmute.rs:LL:CC + | +LL | deref(ptr1, ptr2.with_addr(ptr1.addr())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/provenance/ptr_int_unexposed.rs b/src/tools/miri/tests/fail/provenance/ptr_int_unexposed.rs new file mode 100644 index 0000000000000..07de41d10a038 --- /dev/null +++ b/src/tools/miri/tests/fail/provenance/ptr_int_unexposed.rs @@ -0,0 +1,12 @@ +//@compile-flags: -Zmiri-permissive-provenance +#![feature(strict_provenance)] + +fn main() { + let x: i32 = 3; + let x_ptr = &x as *const i32; + + let x_usize: usize = x_ptr.addr(); + // Cast back an address that did *not* get exposed. + let ptr = std::ptr::from_exposed_addr::(x_usize); + assert_eq!(unsafe { *ptr }, 3); //~ ERROR: is a dangling pointer +} diff --git a/src/tools/miri/tests/fail/provenance/ptr_int_unexposed.stderr b/src/tools/miri/tests/fail/provenance/ptr_int_unexposed.stderr new file mode 100644 index 0000000000000..4ad885ddabdc0 --- /dev/null +++ b/src/tools/miri/tests/fail/provenance/ptr_int_unexposed.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: dereferencing pointer failed: $HEX[noalloc] is a dangling pointer (it has no provenance) + --> $DIR/ptr_int_unexposed.rs:LL:CC + | +LL | assert_eq!(unsafe { *ptr }, 3); + | ^^^^ dereferencing pointer failed: $HEX[noalloc] is a dangling pointer (it has no provenance) + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/ptr_int_unexposed.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/provenance/ptr_invalid.rs b/src/tools/miri/tests/fail/provenance/ptr_invalid.rs new file mode 100644 index 0000000000000..d7d32d83e0771 --- /dev/null +++ b/src/tools/miri/tests/fail/provenance/ptr_invalid.rs @@ -0,0 +1,9 @@ +#![feature(strict_provenance)] + +// Ensure that a `ptr::invalid` ptr is truly invalid. +fn main() { + let x = 42; + let xptr = &x as *const i32; + let xptr_invalid = std::ptr::invalid::(xptr.expose_addr()); + let _val = unsafe { *xptr_invalid }; //~ ERROR: is a dangling pointer +} diff --git a/src/tools/miri/tests/fail/provenance/ptr_invalid.stderr b/src/tools/miri/tests/fail/provenance/ptr_invalid.stderr new file mode 100644 index 0000000000000..ef9dcad97cbdc --- /dev/null +++ b/src/tools/miri/tests/fail/provenance/ptr_invalid.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: dereferencing pointer failed: $HEX[noalloc] is a dangling pointer (it has no provenance) + --> $DIR/ptr_invalid.rs:LL:CC + | +LL | let _val = unsafe { *xptr_invalid }; + | ^^^^^^^^^^^^^ dereferencing pointer failed: $HEX[noalloc] is a dangling pointer (it has no provenance) + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/ptr_invalid.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/provenance/ptr_invalid_offset.rs b/src/tools/miri/tests/fail/provenance/ptr_invalid_offset.rs new file mode 100644 index 0000000000000..91ba18f768055 --- /dev/null +++ b/src/tools/miri/tests/fail/provenance/ptr_invalid_offset.rs @@ -0,0 +1,10 @@ +//@compile-flags: -Zmiri-strict-provenance +#![feature(strict_provenance)] + +fn main() { + let x = 22; + let ptr = &x as *const _ as *const u8; + let roundtrip = std::ptr::invalid::(ptr as usize); + // Not even offsetting this is allowed. + let _ = unsafe { roundtrip.offset(1) }; //~ERROR: is a dangling pointer +} diff --git a/src/tools/miri/tests/fail/provenance/ptr_invalid_offset.stderr b/src/tools/miri/tests/fail/provenance/ptr_invalid_offset.stderr new file mode 100644 index 0000000000000..3607635c8fbe5 --- /dev/null +++ b/src/tools/miri/tests/fail/provenance/ptr_invalid_offset.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: out-of-bounds pointer arithmetic: $HEX[noalloc] is a dangling pointer (it has no provenance) + --> $DIR/ptr_invalid_offset.rs:LL:CC + | +LL | let _ = unsafe { roundtrip.offset(1) }; + | ^^^^^^^^^^^^^^^^^^^ out-of-bounds pointer arithmetic: $HEX[noalloc] is a dangling pointer (it has no provenance) + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/ptr_invalid_offset.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/provenance/strict_provenance_cast.rs b/src/tools/miri/tests/fail/provenance/strict_provenance_cast.rs new file mode 100644 index 0000000000000..04552d0c332fd --- /dev/null +++ b/src/tools/miri/tests/fail/provenance/strict_provenance_cast.rs @@ -0,0 +1,7 @@ +//@compile-flags: -Zmiri-strict-provenance +#![feature(strict_provenance)] + +fn main() { + let addr = &0 as *const i32 as usize; + let _ptr = std::ptr::from_exposed_addr::(addr); //~ ERROR: integer-to-pointer casts and `ptr::from_exposed_addr` are not supported +} diff --git a/src/tools/miri/tests/fail/provenance/strict_provenance_cast.stderr b/src/tools/miri/tests/fail/provenance/strict_provenance_cast.stderr new file mode 100644 index 0000000000000..998ccc8bb49c6 --- /dev/null +++ b/src/tools/miri/tests/fail/provenance/strict_provenance_cast.stderr @@ -0,0 +1,14 @@ +error: unsupported operation: integer-to-pointer casts and `ptr::from_exposed_addr` are not supported with `-Zmiri-strict-provenance` + --> $DIR/strict_provenance_cast.rs:LL:CC + | +LL | let _ptr = std::ptr::from_exposed_addr::(addr); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ integer-to-pointer casts and `ptr::from_exposed_addr` are not supported with `-Zmiri-strict-provenance` + | + = help: use Strict Provenance APIs (https://doc.rust-lang.org/nightly/std/ptr/index.html#strict-provenance, https://crates.io/crates/sptr) instead + = note: BACKTRACE: + = note: inside `main` at $DIR/strict_provenance_cast.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/rc_as_ptr.rs b/src/tools/miri/tests/fail/rc_as_ptr.rs new file mode 100644 index 0000000000000..6aea1870748cc --- /dev/null +++ b/src/tools/miri/tests/fail/rc_as_ptr.rs @@ -0,0 +1,20 @@ +// This should fail even without validation +//@compile-flags: -Zmiri-disable-validation + +use std::ptr; +use std::rc::{Rc, Weak}; + +/// Taken from the `Weak::as_ptr` doctest. +fn main() { + let strong = Rc::new(Box::new(42)); + let weak = Rc::downgrade(&strong); + // Both point to the same object + assert!(ptr::eq(&*strong, Weak::as_ptr(&weak))); + // The strong here keeps it alive, so we can still access the object. + assert_eq!(42, **unsafe { &*Weak::as_ptr(&weak) }); + + drop(strong); + // But not any more. We can do Weak::as_raw(&weak), but accessing the pointer would lead to + // undefined behaviour. + assert_eq!(42, **unsafe { &*Weak::as_ptr(&weak) }); //~ ERROR: dereferenced after this allocation got freed +} diff --git a/src/tools/miri/tests/fail/rc_as_ptr.stderr b/src/tools/miri/tests/fail/rc_as_ptr.stderr new file mode 100644 index 0000000000000..70bdd157bdc34 --- /dev/null +++ b/src/tools/miri/tests/fail/rc_as_ptr.stderr @@ -0,0 +1,16 @@ +error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed + --> $DIR/rc_as_ptr.rs:LL:CC + | +LL | assert_eq!(42, **unsafe { &*Weak::as_ptr(&weak) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ pointer to ALLOC was dereferenced after this allocation got freed + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at RUSTLIB/core/src/macros/mod.rs:LL:CC + = note: this error originates in the macro `assert_eq` (in Nightly builds, run with -Z macro-backtrace for more info) + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/reading_half_a_pointer.rs b/src/tools/miri/tests/fail/reading_half_a_pointer.rs new file mode 100644 index 0000000000000..2d66913262476 --- /dev/null +++ b/src/tools/miri/tests/fail/reading_half_a_pointer.rs @@ -0,0 +1,30 @@ +#![allow(dead_code)] + +// We use packed structs to get around alignment restrictions +#[repr(packed)] +struct Data { + pad: u8, + ptr: &'static i32, +} + +// But we need to gurantee some alignment +struct Wrapper { + align: u64, + data: Data, +} + +static G: i32 = 0; + +fn main() { + let mut w = Wrapper { align: 0, data: Data { pad: 0, ptr: &G } }; + + // Get a pointer to the beginning of the Data struct (one u8 byte, then the pointer bytes). + // Thanks to the wrapper, we know this is aligned-enough to perform a load at ptr size. + // We load at pointer type, so having a relocation is ok -- but here, the relocation + // starts 1 byte to the right, so using it would actually be wrong! + let d_alias = &mut w.data as *mut _ as *mut *const u8; + unsafe { + let x = *d_alias; + let _val = *x; //~ERROR: is a dangling pointer (it has no provenance) + } +} diff --git a/src/tools/miri/tests/fail/reading_half_a_pointer.stderr b/src/tools/miri/tests/fail/reading_half_a_pointer.stderr new file mode 100644 index 0000000000000..61a7161a98bb3 --- /dev/null +++ b/src/tools/miri/tests/fail/reading_half_a_pointer.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: dereferencing pointer failed: $HEX[noalloc] is a dangling pointer (it has no provenance) + --> $DIR/reading_half_a_pointer.rs:LL:CC + | +LL | let _val = *x; + | ^^ dereferencing pointer failed: $HEX[noalloc] is a dangling pointer (it has no provenance) + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/reading_half_a_pointer.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/rustc-error.rs b/src/tools/miri/tests/fail/rustc-error.rs new file mode 100644 index 0000000000000..7fc73bf365d5e --- /dev/null +++ b/src/tools/miri/tests/fail/rustc-error.rs @@ -0,0 +1,4 @@ +// Make sure we exit with non-0 status code when the program fails to build. +fn main() { + println("Hello, world!"); //~ ERROR: expected function, found macro +} diff --git a/src/tools/miri/tests/fail/rustc-error.stderr b/src/tools/miri/tests/fail/rustc-error.stderr new file mode 100644 index 0000000000000..09d0f7a6df955 --- /dev/null +++ b/src/tools/miri/tests/fail/rustc-error.stderr @@ -0,0 +1,14 @@ +error[E0423]: expected function, found macro `println` + --> $DIR/rustc-error.rs:LL:CC + | +LL | println("Hello, world!"); + | ^^^^^^^ not a function + | +help: use `!` to invoke the macro + | +LL | println!("Hello, world!"); + | + + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0423`. diff --git a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-decl.rs b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-decl.rs new file mode 100644 index 0000000000000..97a70103e6461 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-decl.rs @@ -0,0 +1,13 @@ +extern "Rust" { + fn miri_get_backtrace(flags: u64) -> Box<[*mut ()]>; + fn miri_resolve_frame(ptr: *mut (), flags: u64); +} + +fn main() { + let frames = unsafe { miri_get_backtrace(0) }; + for frame in frames.into_iter() { + unsafe { + miri_resolve_frame(*frame, 0); //~ ERROR: Undefined Behavior: bad declaration of miri_resolve_frame - should return a struct with 5 fields + } + } +} diff --git a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-decl.stderr b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-decl.stderr new file mode 100644 index 0000000000000..200f5f56213d6 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-decl.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: bad declaration of miri_resolve_frame - should return a struct with 5 fields + --> $DIR/bad-backtrace-decl.rs:LL:CC + | +LL | ... miri_resolve_frame(*frame, 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ bad declaration of miri_resolve_frame - should return a struct with 5 fields + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/bad-backtrace-decl.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-flags.rs b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-flags.rs new file mode 100644 index 0000000000000..a4e186eaa98a4 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-flags.rs @@ -0,0 +1,9 @@ +extern "Rust" { + fn miri_get_backtrace(flags: u64, buf: *mut *mut ()); +} + +fn main() { + unsafe { + miri_get_backtrace(2, std::ptr::null_mut()); //~ ERROR: unsupported operation: unknown `miri_get_backtrace` flags 2 + } +} diff --git a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-flags.stderr b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-flags.stderr new file mode 100644 index 0000000000000..5d51790f8a5c1 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-flags.stderr @@ -0,0 +1,14 @@ +error: unsupported operation: unknown `miri_get_backtrace` flags 2 + --> $DIR/bad-backtrace-flags.rs:LL:CC + | +LL | miri_get_backtrace(2, std::ptr::null_mut()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unknown `miri_get_backtrace` flags 2 + | + = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support + = note: BACKTRACE: + = note: inside `main` at $DIR/bad-backtrace-flags.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-ptr.rs b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-ptr.rs new file mode 100644 index 0000000000000..843d0d11873d2 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-ptr.rs @@ -0,0 +1,9 @@ +extern "Rust" { + fn miri_resolve_frame(ptr: *mut (), flags: u64); +} + +fn main() { + unsafe { + miri_resolve_frame(std::ptr::null_mut(), 0); //~ ERROR: null pointer is a dangling pointer + } +} diff --git a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-ptr.stderr b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-ptr.stderr new file mode 100644 index 0000000000000..f23f834000aa1 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-ptr.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: out-of-bounds pointer use: null pointer is a dangling pointer (it has no provenance) + --> $DIR/bad-backtrace-ptr.rs:LL:CC + | +LL | miri_resolve_frame(std::ptr::null_mut(), 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ out-of-bounds pointer use: null pointer is a dangling pointer (it has no provenance) + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/bad-backtrace-ptr.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-resolve-flags.rs b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-resolve-flags.rs new file mode 100644 index 0000000000000..31e3915f3d64f --- /dev/null +++ b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-resolve-flags.rs @@ -0,0 +1,25 @@ +#[repr(C)] +struct MiriFrame { + name_len: usize, + filename_len: usize, + lineno: u32, + colno: u32, + fn_ptr: *mut (), +} + +extern "Rust" { + fn miri_backtrace_size(flags: u64) -> usize; + fn miri_get_backtrace(flags: u64, buf: *mut *mut ()); + fn miri_resolve_frame(ptr: *mut (), flags: u64) -> MiriFrame; +} + +fn main() { + unsafe { + let mut buf = vec![std::ptr::null_mut(); miri_backtrace_size(0)]; + + miri_get_backtrace(1, buf.as_mut_ptr()); + + // miri_resolve_frame will error from an invalid backtrace before it will from invalid flags + miri_resolve_frame(buf[0], 2); //~ ERROR: unsupported operation: unknown `miri_resolve_frame` flags 2 + } +} diff --git a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-resolve-flags.stderr b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-resolve-flags.stderr new file mode 100644 index 0000000000000..fe123c2352f0a --- /dev/null +++ b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-resolve-flags.stderr @@ -0,0 +1,14 @@ +error: unsupported operation: unknown `miri_resolve_frame` flags 2 + --> $DIR/bad-backtrace-resolve-flags.rs:LL:CC + | +LL | miri_resolve_frame(buf[0], 2); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unknown `miri_resolve_frame` flags 2 + | + = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support + = note: BACKTRACE: + = note: inside `main` at $DIR/bad-backtrace-resolve-flags.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-resolve-names-flags.rs b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-resolve-names-flags.rs new file mode 100644 index 0000000000000..44c3c025043b6 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-resolve-names-flags.rs @@ -0,0 +1,16 @@ +extern "Rust" { + fn miri_backtrace_size(flags: u64) -> usize; + fn miri_get_backtrace(flags: u64, buf: *mut *mut ()); + fn miri_resolve_frame_names(ptr: *mut (), flags: u64, name_buf: *mut u8, filename_buf: *mut u8); +} + +fn main() { + unsafe { + let mut buf = vec![std::ptr::null_mut(); miri_backtrace_size(0)]; + + miri_get_backtrace(1, buf.as_mut_ptr()); + + // miri_resolve_frame_names will error from an invalid backtrace before it will from invalid flags + miri_resolve_frame_names(buf[0], 2, std::ptr::null_mut(), std::ptr::null_mut()); //~ ERROR: unsupported operation: unknown `miri_resolve_frame_names` flags 2 + } +} diff --git a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-resolve-names-flags.stderr b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-resolve-names-flags.stderr new file mode 100644 index 0000000000000..a3003c9093f72 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-resolve-names-flags.stderr @@ -0,0 +1,14 @@ +error: unsupported operation: unknown `miri_resolve_frame_names` flags 2 + --> $DIR/bad-backtrace-resolve-names-flags.rs:LL:CC + | +LL | ... miri_resolve_frame_names(buf[0], 2, std::ptr::null_mut(), std::ptr::null_mut()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unknown `miri_resolve_frame_names` flags 2 + | + = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support + = note: BACKTRACE: + = note: inside `main` at $DIR/bad-backtrace-resolve-names-flags.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-size-flags.rs b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-size-flags.rs new file mode 100644 index 0000000000000..bba74c71a5e8f --- /dev/null +++ b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-size-flags.rs @@ -0,0 +1,9 @@ +extern "Rust" { + fn miri_backtrace_size(flags: u64) -> usize; +} + +fn main() { + unsafe { + miri_backtrace_size(2); //~ ERROR: unsupported operation: unknown `miri_backtrace_size` flags 2 + } +} diff --git a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-size-flags.stderr b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-size-flags.stderr new file mode 100644 index 0000000000000..b4a02c0e363ed --- /dev/null +++ b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-size-flags.stderr @@ -0,0 +1,14 @@ +error: unsupported operation: unknown `miri_backtrace_size` flags 2 + --> $DIR/bad-backtrace-size-flags.rs:LL:CC + | +LL | miri_backtrace_size(2); + | ^^^^^^^^^^^^^^^^^^^^^^ unknown `miri_backtrace_size` flags 2 + | + = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support + = note: BACKTRACE: + = note: inside `main` at $DIR/bad-backtrace-size-flags.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/shims/fs/close_stdout.rs b/src/tools/miri/tests/fail/shims/fs/close_stdout.rs new file mode 100644 index 0000000000000..09da8509af412 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/fs/close_stdout.rs @@ -0,0 +1,10 @@ +//@ignore-target-windows: No libc on Windows +//@compile-flags: -Zmiri-disable-isolation + +// FIXME: standard handles cannot be closed (https://github.com/rust-lang/rust/issues/40032) + +fn main() { + unsafe { + libc::close(1); //~ ERROR: cannot close stdout + } +} diff --git a/src/tools/miri/tests/fail/shims/fs/close_stdout.stderr b/src/tools/miri/tests/fail/shims/fs/close_stdout.stderr new file mode 100644 index 0000000000000..02f1eee97fc04 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/fs/close_stdout.stderr @@ -0,0 +1,14 @@ +error: unsupported operation: cannot close stdout + --> $DIR/close_stdout.rs:LL:CC + | +LL | libc::close(1); + | ^^^^^^^^^^^^^^ cannot close stdout + | + = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support + = note: BACKTRACE: + = note: inside `main` at $DIR/close_stdout.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/shims/fs/isolated_file.rs b/src/tools/miri/tests/fail/shims/fs/isolated_file.rs new file mode 100644 index 0000000000000..9b664ffe52acd --- /dev/null +++ b/src/tools/miri/tests/fail/shims/fs/isolated_file.rs @@ -0,0 +1,6 @@ +//@ignore-target-windows: File handling is not implemented yet +//@error-pattern: `open` not available when isolation is enabled + +fn main() { + let _file = std::fs::File::open("file.txt").unwrap(); +} diff --git a/src/tools/miri/tests/fail/shims/fs/isolated_file.stderr b/src/tools/miri/tests/fail/shims/fs/isolated_file.stderr new file mode 100644 index 0000000000000..4e3fdc7a45801 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/fs/isolated_file.stderr @@ -0,0 +1,26 @@ +error: unsupported operation: `open` not available when isolation is enabled + --> RUSTLIB/std/src/sys/PLATFORM/fs.rs:LL:CC + | +LL | let fd = cvt_r(|| unsafe { open64(path.as_ptr(), flags, opts.mode as c_int) })?; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `open` not available when isolation is enabled + | + = help: pass the flag `-Zmiri-disable-isolation` to disable isolation; + = help: or pass `-Zmiri-isolation-error=warn` to configure Miri to return an error code from isolated operations (if supported for that operation) and continue with a warning + = note: BACKTRACE: + = note: inside closure at RUSTLIB/std/src/sys/PLATFORM/fs.rs:LL:CC + = note: inside `std::sys::PLATFORM::cvt_r::` at RUSTLIB/std/src/sys/PLATFORM/mod.rs:LL:CC + = note: inside `std::sys::PLATFORM::fs::File::open_c` at RUSTLIB/std/src/sys/PLATFORM/fs.rs:LL:CC + = note: inside `std::sys::PLATFORM::fs::File::open` at RUSTLIB/std/src/sys/PLATFORM/fs.rs:LL:CC + = note: inside `std::fs::OpenOptions::_open` at RUSTLIB/std/src/fs.rs:LL:CC + = note: inside `std::fs::OpenOptions::open::<&std::path::Path>` at RUSTLIB/std/src/fs.rs:LL:CC + = note: inside `std::fs::File::open::<&str>` at RUSTLIB/std/src/fs.rs:LL:CC +note: inside `main` at $DIR/isolated_file.rs:LL:CC + --> $DIR/isolated_file.rs:LL:CC + | +LL | let _file = std::fs::File::open("file.txt").unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/shims/fs/isolated_stdin.rs b/src/tools/miri/tests/fail/shims/fs/isolated_stdin.rs new file mode 100644 index 0000000000000..a45f805696d49 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/fs/isolated_stdin.rs @@ -0,0 +1,9 @@ +//@ignore-target-windows: No libc on Windows + +fn main() -> std::io::Result<()> { + let mut bytes = [0u8; 512]; + unsafe { + libc::read(0, bytes.as_mut_ptr() as *mut libc::c_void, 512); //~ ERROR: `read` from stdin not available when isolation is enabled + } + Ok(()) +} diff --git a/src/tools/miri/tests/fail/shims/fs/isolated_stdin.stderr b/src/tools/miri/tests/fail/shims/fs/isolated_stdin.stderr new file mode 100644 index 0000000000000..ed826147e3bdb --- /dev/null +++ b/src/tools/miri/tests/fail/shims/fs/isolated_stdin.stderr @@ -0,0 +1,15 @@ +error: unsupported operation: `read` from stdin not available when isolation is enabled + --> $DIR/isolated_stdin.rs:LL:CC + | +LL | libc::read(0, bytes.as_mut_ptr() as *mut libc::c_void, 512); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `read` from stdin not available when isolation is enabled + | + = help: pass the flag `-Zmiri-disable-isolation` to disable isolation; + = help: or pass `-Zmiri-isolation-error=warn` to configure Miri to return an error code from isolated operations (if supported for that operation) and continue with a warning + = note: BACKTRACE: + = note: inside `main` at $DIR/isolated_stdin.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/shims/fs/mkstemp_immutable_arg.rs b/src/tools/miri/tests/fail/shims/fs/mkstemp_immutable_arg.rs new file mode 100644 index 0000000000000..ba9f404d7c9ac --- /dev/null +++ b/src/tools/miri/tests/fail/shims/fs/mkstemp_immutable_arg.rs @@ -0,0 +1,11 @@ +//@ignore-target-windows: No libc on Windows +//@compile-flags: -Zmiri-disable-isolation + +fn main() { + test_mkstemp_immutable_arg(); +} + +fn test_mkstemp_immutable_arg() { + let s: *mut libc::c_char = b"fooXXXXXX\0" as *const _ as *mut _; + let _fd = unsafe { libc::mkstemp(s) }; //~ ERROR: Undefined Behavior: writing to alloc1 which is read-only +} diff --git a/src/tools/miri/tests/fail/shims/fs/mkstemp_immutable_arg.stderr b/src/tools/miri/tests/fail/shims/fs/mkstemp_immutable_arg.stderr new file mode 100644 index 0000000000000..414ac1cb1b702 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/fs/mkstemp_immutable_arg.stderr @@ -0,0 +1,20 @@ +error: Undefined Behavior: writing to ALLOC which is read-only + --> $DIR/mkstemp_immutable_arg.rs:LL:CC + | +LL | let _fd = unsafe { libc::mkstemp(s) }; + | ^^^^^^^^^^^^^^^^ writing to ALLOC which is read-only + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `test_mkstemp_immutable_arg` at $DIR/mkstemp_immutable_arg.rs:LL:CC +note: inside `main` at $DIR/mkstemp_immutable_arg.rs:LL:CC + --> $DIR/mkstemp_immutable_arg.rs:LL:CC + | +LL | test_mkstemp_immutable_arg(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/shims/fs/read_from_stdout.rs b/src/tools/miri/tests/fail/shims/fs/read_from_stdout.rs new file mode 100644 index 0000000000000..073fca4712e9a --- /dev/null +++ b/src/tools/miri/tests/fail/shims/fs/read_from_stdout.rs @@ -0,0 +1,10 @@ +//@compile-flags: -Zmiri-disable-isolation +//@ignore-target-windows: No libc on Windows + +fn main() -> std::io::Result<()> { + let mut bytes = [0u8; 512]; + unsafe { + libc::read(1, bytes.as_mut_ptr() as *mut libc::c_void, 512); //~ ERROR: cannot read from stdout + } + Ok(()) +} diff --git a/src/tools/miri/tests/fail/shims/fs/read_from_stdout.stderr b/src/tools/miri/tests/fail/shims/fs/read_from_stdout.stderr new file mode 100644 index 0000000000000..bcece7ad4e55d --- /dev/null +++ b/src/tools/miri/tests/fail/shims/fs/read_from_stdout.stderr @@ -0,0 +1,14 @@ +error: unsupported operation: cannot read from stdout + --> $DIR/read_from_stdout.rs:LL:CC + | +LL | libc::read(1, bytes.as_mut_ptr() as *mut libc::c_void, 512); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot read from stdout + | + = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support + = note: BACKTRACE: + = note: inside `main` at $DIR/read_from_stdout.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/shims/fs/unix_open_missing_required_mode.rs b/src/tools/miri/tests/fail/shims/fs/unix_open_missing_required_mode.rs new file mode 100644 index 0000000000000..ae231d4be667e --- /dev/null +++ b/src/tools/miri/tests/fail/shims/fs/unix_open_missing_required_mode.rs @@ -0,0 +1,12 @@ +//@ignore-target-windows: No libc on Windows +//@compile-flags: -Zmiri-disable-isolation + +fn main() { + test_file_open_missing_needed_mode(); +} + +fn test_file_open_missing_needed_mode() { + let name = b"missing_arg.txt\0"; + let name_ptr = name.as_ptr().cast::(); + let _fd = unsafe { libc::open(name_ptr, libc::O_CREAT) }; //~ ERROR: Undefined Behavior: incorrect number of arguments for `open` with `O_CREAT`: got 2, expected at least 3 +} diff --git a/src/tools/miri/tests/fail/shims/fs/unix_open_missing_required_mode.stderr b/src/tools/miri/tests/fail/shims/fs/unix_open_missing_required_mode.stderr new file mode 100644 index 0000000000000..38d033b494554 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/fs/unix_open_missing_required_mode.stderr @@ -0,0 +1,20 @@ +error: Undefined Behavior: incorrect number of arguments for `open` with `O_CREAT`: got 2, expected at least 3 + --> $DIR/unix_open_missing_required_mode.rs:LL:CC + | +LL | ...safe { libc::open(name_ptr, libc::O_CREAT) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ incorrect number of arguments for `open` with `O_CREAT`: got 2, expected at least 3 + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `test_file_open_missing_needed_mode` at $DIR/unix_open_missing_required_mode.rs:LL:CC +note: inside `main` at $DIR/unix_open_missing_required_mode.rs:LL:CC + --> $DIR/unix_open_missing_required_mode.rs:LL:CC + | +LL | test_file_open_missing_needed_mode(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/shims/fs/write_to_stdin.rs b/src/tools/miri/tests/fail/shims/fs/write_to_stdin.rs new file mode 100644 index 0000000000000..d039ad718d339 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/fs/write_to_stdin.rs @@ -0,0 +1,9 @@ +//@ignore-target-windows: No libc on Windows + +fn main() -> std::io::Result<()> { + let bytes = b"hello"; + unsafe { + libc::write(0, bytes.as_ptr() as *const libc::c_void, 5); //~ ERROR: cannot write to stdin + } + Ok(()) +} diff --git a/src/tools/miri/tests/fail/shims/fs/write_to_stdin.stderr b/src/tools/miri/tests/fail/shims/fs/write_to_stdin.stderr new file mode 100644 index 0000000000000..d4a38e1ca9615 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/fs/write_to_stdin.stderr @@ -0,0 +1,14 @@ +error: unsupported operation: cannot write to stdin + --> $DIR/write_to_stdin.rs:LL:CC + | +LL | libc::write(0, bytes.as_ptr() as *const libc::c_void, 5); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot write to stdin + | + = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support + = note: BACKTRACE: + = note: inside `main` at $DIR/write_to_stdin.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/shims/shim_arg_size.rs b/src/tools/miri/tests/fail/shims/shim_arg_size.rs new file mode 100644 index 0000000000000..3d7bc25bf5d31 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/shim_arg_size.rs @@ -0,0 +1,10 @@ +fn main() { + extern "C" { + // Use the wrong type (ie. not `i32`) for the `c` argument. + fn memchr(s: *const std::ffi::c_void, c: u8, n: usize) -> *mut std::ffi::c_void; + } + + unsafe { + memchr(std::ptr::null(), 0, 0); //~ ERROR: Undefined Behavior: scalar size mismatch + }; +} diff --git a/src/tools/miri/tests/fail/shims/shim_arg_size.stderr b/src/tools/miri/tests/fail/shims/shim_arg_size.stderr new file mode 100644 index 0000000000000..d951f81810ef6 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/shim_arg_size.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: scalar size mismatch: expected 4 bytes but got 1 bytes instead + --> $DIR/shim_arg_size.rs:LL:CC + | +LL | memchr(std::ptr::null(), 0, 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ scalar size mismatch: expected 4 bytes but got 1 bytes instead + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/shim_arg_size.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_cond_double_destroy.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_cond_double_destroy.rs new file mode 100644 index 0000000000000..94ca3496ed948 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_cond_double_destroy.rs @@ -0,0 +1,20 @@ +//@ignore-target-windows: No libc on Windows + +/// Test that destroying a pthread_cond twice fails, even without a check for number validity + +fn main() { + unsafe { + use core::mem::MaybeUninit; + let mut attr = MaybeUninit::::uninit(); + libc::pthread_condattr_init(attr.as_mut_ptr()); + + let mut cond = MaybeUninit::::uninit(); + + libc::pthread_cond_init(cond.as_mut_ptr(), attr.as_ptr()); + + libc::pthread_cond_destroy(cond.as_mut_ptr()); + + libc::pthread_cond_destroy(cond.as_mut_ptr()); + //~^ ERROR: Undefined Behavior: using uninitialized data, but this operation requires initialized memory + } +} diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_cond_double_destroy.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_cond_double_destroy.stderr new file mode 100644 index 0000000000000..ecfedf753703d --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_cond_double_destroy.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: using uninitialized data, but this operation requires initialized memory + --> $DIR/libc_pthread_cond_double_destroy.rs:LL:CC + | +LL | libc::pthread_cond_destroy(cond.as_mut_ptr()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ using uninitialized data, but this operation requires initialized memory + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/libc_pthread_cond_double_destroy.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_condattr_double_destroy.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_condattr_double_destroy.rs new file mode 100644 index 0000000000000..13e639a867dcc --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_condattr_double_destroy.rs @@ -0,0 +1,17 @@ +//@ignore-target-windows: No libc on Windows + +/// Test that destroying a pthread_condattr twice fails, even without a check for number validity + +fn main() { + unsafe { + use core::mem::MaybeUninit; + let mut attr = MaybeUninit::::uninit(); + + libc::pthread_condattr_init(attr.as_mut_ptr()); + + libc::pthread_condattr_destroy(attr.as_mut_ptr()); + + libc::pthread_condattr_destroy(attr.as_mut_ptr()); + //~^ ERROR: Undefined Behavior: using uninitialized data, but this operation requires initialized memory + } +} diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_condattr_double_destroy.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_condattr_double_destroy.stderr new file mode 100644 index 0000000000000..f39d909adbd64 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_condattr_double_destroy.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: using uninitialized data, but this operation requires initialized memory + --> $DIR/libc_pthread_condattr_double_destroy.rs:LL:CC + | +LL | libc::pthread_condattr_destroy(attr.as_mut_ptr()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ using uninitialized data, but this operation requires initialized memory + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/libc_pthread_condattr_double_destroy.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_NULL_deadlock.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_NULL_deadlock.rs new file mode 100644 index 0000000000000..8b2510733831f --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_NULL_deadlock.rs @@ -0,0 +1,12 @@ +//@ignore-target-windows: No libc on Windows +// +// Check that if we pass NULL attribute, then we get the default mutex type. + +fn main() { + unsafe { + let mut mutex: libc::pthread_mutex_t = std::mem::zeroed(); + assert_eq!(libc::pthread_mutex_init(&mut mutex as *mut _, std::ptr::null() as *const _), 0); + assert_eq!(libc::pthread_mutex_lock(&mut mutex as *mut _), 0); + libc::pthread_mutex_lock(&mut mutex as *mut _); //~ ERROR: Undefined Behavior: trying to acquire already locked default mutex + } +} diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_NULL_deadlock.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_NULL_deadlock.stderr new file mode 100644 index 0000000000000..4a138e6f8a25c --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_NULL_deadlock.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: trying to acquire already locked default mutex + --> $DIR/libc_pthread_mutex_NULL_deadlock.rs:LL:CC + | +LL | libc::pthread_mutex_lock(&mut mutex as *mut _); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trying to acquire already locked default mutex + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/libc_pthread_mutex_NULL_deadlock.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_deadlock.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_deadlock.rs new file mode 100644 index 0000000000000..6c3cb738e2997 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_deadlock.rs @@ -0,0 +1,28 @@ +//@ignore-target-windows: No libc on Windows + +use std::cell::UnsafeCell; +use std::sync::Arc; +use std::thread; + +struct Mutex(UnsafeCell); + +unsafe impl Send for Mutex {} +unsafe impl Sync for Mutex {} + +fn new_lock() -> Arc { + Arc::new(Mutex(UnsafeCell::new(libc::PTHREAD_MUTEX_INITIALIZER))) +} + +fn main() { + unsafe { + let lock = new_lock(); + assert_eq!(libc::pthread_mutex_lock(lock.0.get() as *mut _), 0); + + let lock_copy = lock.clone(); + thread::spawn(move || { + assert_eq!(libc::pthread_mutex_lock(lock_copy.0.get() as *mut _), 0); //~ ERROR: deadlock + }) + .join() + .unwrap(); + } +} diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_deadlock.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_deadlock.stderr new file mode 100644 index 0000000000000..599655a8692b1 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_deadlock.stderr @@ -0,0 +1,12 @@ +error: deadlock: the evaluated program deadlocked + --> $DIR/libc_pthread_mutex_deadlock.rs:LL:CC + | +LL | assert_eq!(libc::pthread_mutex_lock(lock_copy.0.get() as *mut _), 0); + | ^ the evaluated program deadlocked + | + = note: inside closure at $DIR/libc_pthread_mutex_deadlock.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_default_deadlock.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_default_deadlock.rs new file mode 100644 index 0000000000000..f443768819f96 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_default_deadlock.rs @@ -0,0 +1,13 @@ +//@ignore-target-windows: No libc on Windows +// +// Check that if we do not set the mutex type, it is the default. + +fn main() { + unsafe { + let mutexattr: libc::pthread_mutexattr_t = std::mem::zeroed(); + let mut mutex: libc::pthread_mutex_t = std::mem::zeroed(); + assert_eq!(libc::pthread_mutex_init(&mut mutex as *mut _, &mutexattr as *const _), 0); + assert_eq!(libc::pthread_mutex_lock(&mut mutex as *mut _), 0); + libc::pthread_mutex_lock(&mut mutex as *mut _); //~ ERROR: Undefined Behavior: trying to acquire already locked default mutex + } +} diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_default_deadlock.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_default_deadlock.stderr new file mode 100644 index 0000000000000..8aea3f5c6932f --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_default_deadlock.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: trying to acquire already locked default mutex + --> $DIR/libc_pthread_mutex_default_deadlock.rs:LL:CC + | +LL | libc::pthread_mutex_lock(&mut mutex as *mut _); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trying to acquire already locked default mutex + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/libc_pthread_mutex_default_deadlock.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_destroy_locked.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_destroy_locked.rs new file mode 100644 index 0000000000000..ec3965c7574eb --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_destroy_locked.rs @@ -0,0 +1,15 @@ +//@ignore-target-windows: No libc on Windows + +fn main() { + unsafe { + let mut mutexattr: libc::pthread_mutexattr_t = std::mem::zeroed(); + assert_eq!( + libc::pthread_mutexattr_settype(&mut mutexattr as *mut _, libc::PTHREAD_MUTEX_NORMAL), + 0, + ); + let mut mutex: libc::pthread_mutex_t = std::mem::zeroed(); + assert_eq!(libc::pthread_mutex_init(&mut mutex as *mut _, &mutexattr as *const _), 0); + assert_eq!(libc::pthread_mutex_lock(&mut mutex as *mut _), 0); + libc::pthread_mutex_destroy(&mut mutex as *mut _); //~ ERROR: destroyed a locked mutex + } +} diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_destroy_locked.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_destroy_locked.stderr new file mode 100644 index 0000000000000..a8ab948116e14 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_destroy_locked.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: destroyed a locked mutex + --> $DIR/libc_pthread_mutex_destroy_locked.rs:LL:CC + | +LL | libc::pthread_mutex_destroy(&mut mutex as *mut _); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ destroyed a locked mutex + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/libc_pthread_mutex_destroy_locked.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_double_destroy.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_double_destroy.rs new file mode 100644 index 0000000000000..622c3eaeae30d --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_double_destroy.rs @@ -0,0 +1,21 @@ +//@ignore-target-windows: No libc on Windows + +/// Test that destroying a pthread_mutex twice fails, even without a check for number validity + +fn main() { + unsafe { + use core::mem::MaybeUninit; + + let mut attr = MaybeUninit::::uninit(); + libc::pthread_mutexattr_init(attr.as_mut_ptr()); + + let mut mutex = MaybeUninit::::uninit(); + + libc::pthread_mutex_init(mutex.as_mut_ptr(), attr.as_ptr()); + + libc::pthread_mutex_destroy(mutex.as_mut_ptr()); + + libc::pthread_mutex_destroy(mutex.as_mut_ptr()); + //~^ ERROR: Undefined Behavior: using uninitialized data, but this operation requires initialized memory + } +} diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_double_destroy.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_double_destroy.stderr new file mode 100644 index 0000000000000..9620fdbd18b2f --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_double_destroy.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: using uninitialized data, but this operation requires initialized memory + --> $DIR/libc_pthread_mutex_double_destroy.rs:LL:CC + | +LL | libc::pthread_mutex_destroy(mutex.as_mut_ptr()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ using uninitialized data, but this operation requires initialized memory + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/libc_pthread_mutex_double_destroy.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_normal_deadlock.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_normal_deadlock.rs new file mode 100644 index 0000000000000..5ea09fa5aac3d --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_normal_deadlock.rs @@ -0,0 +1,15 @@ +//@ignore-target-windows: No libc on Windows + +fn main() { + unsafe { + let mut mutexattr: libc::pthread_mutexattr_t = std::mem::zeroed(); + assert_eq!( + libc::pthread_mutexattr_settype(&mut mutexattr as *mut _, libc::PTHREAD_MUTEX_NORMAL), + 0, + ); + let mut mutex: libc::pthread_mutex_t = std::mem::zeroed(); + assert_eq!(libc::pthread_mutex_init(&mut mutex as *mut _, &mutexattr as *const _), 0); + assert_eq!(libc::pthread_mutex_lock(&mut mutex as *mut _), 0); + libc::pthread_mutex_lock(&mut mutex as *mut _); //~ ERROR: deadlock: the evaluated program deadlocked + } +} diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_normal_deadlock.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_normal_deadlock.stderr new file mode 100644 index 0000000000000..b7877d3aa397d --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_normal_deadlock.stderr @@ -0,0 +1,12 @@ +error: deadlock: the evaluated program deadlocked + --> $DIR/libc_pthread_mutex_normal_deadlock.rs:LL:CC + | +LL | libc::pthread_mutex_lock(&mut mutex as *mut _); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program deadlocked + | + = note: inside `main` at $DIR/libc_pthread_mutex_normal_deadlock.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_normal_unlock_unlocked.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_normal_unlock_unlocked.rs new file mode 100644 index 0000000000000..8ce7542edb87f --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_normal_unlock_unlocked.rs @@ -0,0 +1,16 @@ +//@ignore-target-windows: No libc on Windows + +fn main() { + unsafe { + let mut mutexattr: libc::pthread_mutexattr_t = std::mem::zeroed(); + assert_eq!( + libc::pthread_mutexattr_settype(&mut mutexattr as *mut _, libc::PTHREAD_MUTEX_NORMAL), + 0, + ); + let mut mutex: libc::pthread_mutex_t = std::mem::zeroed(); + assert_eq!(libc::pthread_mutex_init(&mut mutex as *mut _, &mutexattr as *const _), 0); + assert_eq!(libc::pthread_mutex_lock(&mut mutex as *mut _), 0); + assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), 0); + libc::pthread_mutex_unlock(&mut mutex as *mut _); //~ ERROR: was not locked + } +} diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_normal_unlock_unlocked.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_normal_unlock_unlocked.stderr new file mode 100644 index 0000000000000..754137b85b9af --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_normal_unlock_unlocked.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: unlocked a PTHREAD_MUTEX_NORMAL mutex that was not locked by the current thread + --> $DIR/libc_pthread_mutex_normal_unlock_unlocked.rs:LL:CC + | +LL | libc::pthread_mutex_unlock(&mut mutex as *mut _); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unlocked a PTHREAD_MUTEX_NORMAL mutex that was not locked by the current thread + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/libc_pthread_mutex_normal_unlock_unlocked.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_wrong_owner.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_wrong_owner.rs new file mode 100644 index 0000000000000..b56775252e4b4 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_wrong_owner.rs @@ -0,0 +1,28 @@ +//@ignore-target-windows: No libc on Windows + +use std::cell::UnsafeCell; +use std::sync::Arc; +use std::thread; + +struct Mutex(UnsafeCell); + +unsafe impl Send for Mutex {} +unsafe impl Sync for Mutex {} + +fn new_lock() -> Arc { + Arc::new(Mutex(UnsafeCell::new(libc::PTHREAD_MUTEX_INITIALIZER))) +} + +fn main() { + unsafe { + let lock = new_lock(); + assert_eq!(libc::pthread_mutex_lock(lock.0.get() as *mut _), 0); + + let lock_copy = lock.clone(); + thread::spawn(move || { + assert_eq!(libc::pthread_mutex_unlock(lock_copy.0.get() as *mut _), 0); //~ ERROR: Undefined Behavior: unlocked a default mutex that was not locked by the current thread + }) + .join() + .unwrap(); + } +} diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_wrong_owner.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_wrong_owner.stderr new file mode 100644 index 0000000000000..aa81b06fc80af --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutex_wrong_owner.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: unlocked a default mutex that was not locked by the current thread + --> $DIR/libc_pthread_mutex_wrong_owner.rs:LL:CC + | +LL | ...t_eq!(libc::pthread_mutex_unlock(lock_copy.0.get() as *mut _), 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unlocked a default mutex that was not locked by the current thread + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside closure at $DIR/libc_pthread_mutex_wrong_owner.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutexattr_double_destroy.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutexattr_double_destroy.rs new file mode 100644 index 0000000000000..474a277516d94 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutexattr_double_destroy.rs @@ -0,0 +1,17 @@ +//@ignore-target-windows: No libc on Windows + +/// Test that destroying a pthread_mutexattr twice fails, even without a check for number validity + +fn main() { + unsafe { + use core::mem::MaybeUninit; + let mut attr = MaybeUninit::::uninit(); + + libc::pthread_mutexattr_init(attr.as_mut_ptr()); + + libc::pthread_mutexattr_destroy(attr.as_mut_ptr()); + + libc::pthread_mutexattr_destroy(attr.as_mut_ptr()); + //~^ ERROR: Undefined Behavior: using uninitialized data, but this operation requires initialized memory + } +} diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutexattr_double_destroy.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutexattr_double_destroy.stderr new file mode 100644 index 0000000000000..82949047d2aab --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_mutexattr_double_destroy.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: using uninitialized data, but this operation requires initialized memory + --> $DIR/libc_pthread_mutexattr_double_destroy.rs:LL:CC + | +LL | libc::pthread_mutexattr_destroy(attr.as_mut_ptr()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ using uninitialized data, but this operation requires initialized memory + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/libc_pthread_mutexattr_double_destroy.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_destroy_read_locked.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_destroy_read_locked.rs new file mode 100644 index 0000000000000..603580ff58abd --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_destroy_read_locked.rs @@ -0,0 +1,9 @@ +//@ignore-target-windows: No libc on Windows + +fn main() { + let rw = std::cell::UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER); + unsafe { + assert_eq!(libc::pthread_rwlock_rdlock(rw.get()), 0); + libc::pthread_rwlock_destroy(rw.get()); //~ ERROR: destroyed a locked rwlock + } +} diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_destroy_read_locked.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_destroy_read_locked.stderr new file mode 100644 index 0000000000000..be73e7f1e2ad4 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_destroy_read_locked.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: destroyed a locked rwlock + --> $DIR/libc_pthread_rwlock_destroy_read_locked.rs:LL:CC + | +LL | libc::pthread_rwlock_destroy(rw.get()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ destroyed a locked rwlock + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/libc_pthread_rwlock_destroy_read_locked.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_destroy_write_locked.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_destroy_write_locked.rs new file mode 100644 index 0000000000000..ae44f22d146ca --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_destroy_write_locked.rs @@ -0,0 +1,9 @@ +//@ignore-target-windows: No libc on Windows + +fn main() { + let rw = std::cell::UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER); + unsafe { + assert_eq!(libc::pthread_rwlock_wrlock(rw.get()), 0); + libc::pthread_rwlock_destroy(rw.get()); //~ ERROR: destroyed a locked rwlock + } +} diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_destroy_write_locked.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_destroy_write_locked.stderr new file mode 100644 index 0000000000000..bc2713a5ffbfa --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_destroy_write_locked.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: destroyed a locked rwlock + --> $DIR/libc_pthread_rwlock_destroy_write_locked.rs:LL:CC + | +LL | libc::pthread_rwlock_destroy(rw.get()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ destroyed a locked rwlock + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/libc_pthread_rwlock_destroy_write_locked.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_double_destroy.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_double_destroy.rs new file mode 100644 index 0000000000000..800986f7506c0 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_double_destroy.rs @@ -0,0 +1,14 @@ +//@ignore-target-windows: No libc on Windows + +/// Test that destroying a pthread_rwlock twice fails, even without a check for number validity + +fn main() { + unsafe { + let mut lock = libc::PTHREAD_RWLOCK_INITIALIZER; + + libc::pthread_rwlock_destroy(&mut lock); + + libc::pthread_rwlock_destroy(&mut lock); + //~^ ERROR: Undefined Behavior: using uninitialized data, but this operation requires initialized memory + } +} diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_double_destroy.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_double_destroy.stderr new file mode 100644 index 0000000000000..5004f84358da8 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_double_destroy.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: using uninitialized data, but this operation requires initialized memory + --> $DIR/libc_pthread_rwlock_double_destroy.rs:LL:CC + | +LL | libc::pthread_rwlock_destroy(&mut lock); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ using uninitialized data, but this operation requires initialized memory + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/libc_pthread_rwlock_double_destroy.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_read_write_deadlock_single_thread.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_read_write_deadlock_single_thread.rs new file mode 100644 index 0000000000000..782c95b6d2e3c --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_read_write_deadlock_single_thread.rs @@ -0,0 +1,9 @@ +//@ignore-target-windows: No libc on Windows + +fn main() { + let rw = std::cell::UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER); + unsafe { + assert_eq!(libc::pthread_rwlock_rdlock(rw.get()), 0); + libc::pthread_rwlock_wrlock(rw.get()); //~ ERROR: deadlock + } +} diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_read_write_deadlock_single_thread.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_read_write_deadlock_single_thread.stderr new file mode 100644 index 0000000000000..075c8f0ef529c --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_read_write_deadlock_single_thread.stderr @@ -0,0 +1,12 @@ +error: deadlock: the evaluated program deadlocked + --> $DIR/libc_pthread_rwlock_read_write_deadlock_single_thread.rs:LL:CC + | +LL | libc::pthread_rwlock_wrlock(rw.get()); + | ^ the evaluated program deadlocked + | + = note: inside `main` at $DIR/libc_pthread_rwlock_read_write_deadlock_single_thread.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_read_wrong_owner.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_read_wrong_owner.rs new file mode 100644 index 0000000000000..1b498ad8fcdb4 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_read_wrong_owner.rs @@ -0,0 +1,28 @@ +//@ignore-target-windows: No libc on Windows + +use std::cell::UnsafeCell; +use std::sync::Arc; +use std::thread; + +struct RwLock(UnsafeCell); + +unsafe impl Send for RwLock {} +unsafe impl Sync for RwLock {} + +fn new_lock() -> Arc { + Arc::new(RwLock(UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER))) +} + +fn main() { + unsafe { + let lock = new_lock(); + assert_eq!(libc::pthread_rwlock_rdlock(lock.0.get() as *mut _), 0); + + let lock_copy = lock.clone(); + thread::spawn(move || { + assert_eq!(libc::pthread_rwlock_unlock(lock_copy.0.get() as *mut _), 0); //~ ERROR: Undefined Behavior: unlocked an rwlock that was not locked by the active thread + }) + .join() + .unwrap(); + } +} diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_read_wrong_owner.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_read_wrong_owner.stderr new file mode 100644 index 0000000000000..7dfa27b43d073 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_read_wrong_owner.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: unlocked an rwlock that was not locked by the active thread + --> $DIR/libc_pthread_rwlock_read_wrong_owner.rs:LL:CC + | +LL | ... assert_eq!(libc::pthread_rwlock_unlock(lock_copy.0.get() as *mut _), 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unlocked an rwlock that was not locked by the active thread + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside closure at $DIR/libc_pthread_rwlock_read_wrong_owner.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_unlock_unlocked.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_unlock_unlocked.rs new file mode 100644 index 0000000000000..05f7e7a06c57f --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_unlock_unlocked.rs @@ -0,0 +1,8 @@ +//@ignore-target-windows: No libc on Windows + +fn main() { + let rw = std::cell::UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER); + unsafe { + libc::pthread_rwlock_unlock(rw.get()); //~ ERROR: was not locked + } +} diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_unlock_unlocked.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_unlock_unlocked.stderr new file mode 100644 index 0000000000000..1c25ac2c048fb --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_unlock_unlocked.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: unlocked an rwlock that was not locked by the active thread + --> $DIR/libc_pthread_rwlock_unlock_unlocked.rs:LL:CC + | +LL | libc::pthread_rwlock_unlock(rw.get()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unlocked an rwlock that was not locked by the active thread + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/libc_pthread_rwlock_unlock_unlocked.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_read_deadlock.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_read_deadlock.rs new file mode 100644 index 0000000000000..201844615e182 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_read_deadlock.rs @@ -0,0 +1,28 @@ +//@ignore-target-windows: No libc on Windows + +use std::cell::UnsafeCell; +use std::sync::Arc; +use std::thread; + +struct RwLock(UnsafeCell); + +unsafe impl Send for RwLock {} +unsafe impl Sync for RwLock {} + +fn new_lock() -> Arc { + Arc::new(RwLock(UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER))) +} + +fn main() { + unsafe { + let lock = new_lock(); + assert_eq!(libc::pthread_rwlock_rdlock(lock.0.get() as *mut _), 0); + + let lock_copy = lock.clone(); + thread::spawn(move || { + assert_eq!(libc::pthread_rwlock_wrlock(lock_copy.0.get() as *mut _), 0); //~ ERROR: deadlock + }) + .join() + .unwrap(); + } +} diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_read_deadlock.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_read_deadlock.stderr new file mode 100644 index 0000000000000..333fb1afb91b7 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_read_deadlock.stderr @@ -0,0 +1,12 @@ +error: deadlock: the evaluated program deadlocked + --> $DIR/libc_pthread_rwlock_write_read_deadlock.rs:LL:CC + | +LL | assert_eq!(libc::pthread_rwlock_wrlock(lock_copy.0.get() as *mut _), 0); + | ^ the evaluated program deadlocked + | + = note: inside closure at $DIR/libc_pthread_rwlock_write_read_deadlock.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_read_deadlock_single_thread.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_read_deadlock_single_thread.rs new file mode 100644 index 0000000000000..538f14ef89f20 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_read_deadlock_single_thread.rs @@ -0,0 +1,9 @@ +//@ignore-target-windows: No libc on Windows + +fn main() { + let rw = std::cell::UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER); + unsafe { + assert_eq!(libc::pthread_rwlock_wrlock(rw.get()), 0); + libc::pthread_rwlock_rdlock(rw.get()); //~ ERROR: deadlock + } +} diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_read_deadlock_single_thread.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_read_deadlock_single_thread.stderr new file mode 100644 index 0000000000000..caab19a782f97 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_read_deadlock_single_thread.stderr @@ -0,0 +1,12 @@ +error: deadlock: the evaluated program deadlocked + --> $DIR/libc_pthread_rwlock_write_read_deadlock_single_thread.rs:LL:CC + | +LL | libc::pthread_rwlock_rdlock(rw.get()); + | ^ the evaluated program deadlocked + | + = note: inside `main` at $DIR/libc_pthread_rwlock_write_read_deadlock_single_thread.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_write_deadlock.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_write_deadlock.rs new file mode 100644 index 0000000000000..b1d7e0492e5a2 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_write_deadlock.rs @@ -0,0 +1,28 @@ +//@ignore-target-windows: No libc on Windows + +use std::cell::UnsafeCell; +use std::sync::Arc; +use std::thread; + +struct RwLock(UnsafeCell); + +unsafe impl Send for RwLock {} +unsafe impl Sync for RwLock {} + +fn new_lock() -> Arc { + Arc::new(RwLock(UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER))) +} + +fn main() { + unsafe { + let lock = new_lock(); + assert_eq!(libc::pthread_rwlock_wrlock(lock.0.get() as *mut _), 0); + + let lock_copy = lock.clone(); + thread::spawn(move || { + assert_eq!(libc::pthread_rwlock_wrlock(lock_copy.0.get() as *mut _), 0); //~ ERROR: deadlock + }) + .join() + .unwrap(); + } +} diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_write_deadlock.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_write_deadlock.stderr new file mode 100644 index 0000000000000..93bede54fcf18 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_write_deadlock.stderr @@ -0,0 +1,12 @@ +error: deadlock: the evaluated program deadlocked + --> $DIR/libc_pthread_rwlock_write_write_deadlock.rs:LL:CC + | +LL | assert_eq!(libc::pthread_rwlock_wrlock(lock_copy.0.get() as *mut _), 0); + | ^ the evaluated program deadlocked + | + = note: inside closure at $DIR/libc_pthread_rwlock_write_write_deadlock.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_write_deadlock_single_thread.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_write_deadlock_single_thread.rs new file mode 100644 index 0000000000000..2c963d36510e6 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_write_deadlock_single_thread.rs @@ -0,0 +1,9 @@ +//@ignore-target-windows: No libc on Windows + +fn main() { + let rw = std::cell::UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER); + unsafe { + assert_eq!(libc::pthread_rwlock_wrlock(rw.get()), 0); + libc::pthread_rwlock_wrlock(rw.get()); //~ ERROR: deadlock + } +} diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_write_deadlock_single_thread.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_write_deadlock_single_thread.stderr new file mode 100644 index 0000000000000..30f5f447c717c --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_write_deadlock_single_thread.stderr @@ -0,0 +1,12 @@ +error: deadlock: the evaluated program deadlocked + --> $DIR/libc_pthread_rwlock_write_write_deadlock_single_thread.rs:LL:CC + | +LL | libc::pthread_rwlock_wrlock(rw.get()); + | ^ the evaluated program deadlocked + | + = note: inside `main` at $DIR/libc_pthread_rwlock_write_write_deadlock_single_thread.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_wrong_owner.rs b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_wrong_owner.rs new file mode 100644 index 0000000000000..dd099474d8fed --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_wrong_owner.rs @@ -0,0 +1,28 @@ +//@ignore-target-windows: No libc on Windows + +use std::cell::UnsafeCell; +use std::sync::Arc; +use std::thread; + +struct RwLock(UnsafeCell); + +unsafe impl Send for RwLock {} +unsafe impl Sync for RwLock {} + +fn new_lock() -> Arc { + Arc::new(RwLock(UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER))) +} + +fn main() { + unsafe { + let lock = new_lock(); + assert_eq!(libc::pthread_rwlock_wrlock(lock.0.get() as *mut _), 0); + + let lock_copy = lock.clone(); + thread::spawn(move || { + assert_eq!(libc::pthread_rwlock_unlock(lock_copy.0.get() as *mut _), 0); //~ ERROR: Undefined Behavior: unlocked an rwlock that was not locked by the active thread + }) + .join() + .unwrap(); + } +} diff --git a/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_wrong_owner.stderr b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_wrong_owner.stderr new file mode 100644 index 0000000000000..5bf402c775ae5 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/sync/libc_pthread_rwlock_write_wrong_owner.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: unlocked an rwlock that was not locked by the active thread + --> $DIR/libc_pthread_rwlock_write_wrong_owner.rs:LL:CC + | +LL | ... assert_eq!(libc::pthread_rwlock_unlock(lock_copy.0.get() as *mut _), 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unlocked an rwlock that was not locked by the active thread + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside closure at $DIR/libc_pthread_rwlock_write_wrong_owner.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/should-pass/cpp20_rwc_syncs.rs b/src/tools/miri/tests/fail/should-pass/cpp20_rwc_syncs.rs new file mode 100644 index 0000000000000..545875a582a46 --- /dev/null +++ b/src/tools/miri/tests/fail/should-pass/cpp20_rwc_syncs.rs @@ -0,0 +1,87 @@ +//@compile-flags: -Zmiri-ignore-leaks + +// https://plv.mpi-sws.org/scfix/paper.pdf +// 2.2 Second Problem: SC Fences are Too Weak +// This test should pass under the C++20 model Rust is using. +// Unfortunately, Miri's weak memory emulation only follows the C++11 model +// as we don't know how to correctly emulate C++20's revised SC semantics, +// so we have to stick to C++11 emulation from existing research. + +use std::sync::atomic::Ordering::*; +use std::sync::atomic::{fence, AtomicUsize}; +use std::thread::spawn; + +// Spins until it reads the given value +fn reads_value(loc: &AtomicUsize, val: usize) -> usize { + while loc.load(Relaxed) != val { + std::hint::spin_loop(); + } + val +} + +// We can't create static items because we need to run each test +// multiple tests +fn static_atomic(val: usize) -> &'static AtomicUsize { + let ret = Box::leak(Box::new(AtomicUsize::new(val))); + // A workaround to put the initialization value in the store buffer. + // See https://github.com/rust-lang/miri/issues/2164 + ret.load(Relaxed); + ret +} + +fn test_cpp20_rwc_syncs() { + /* + int main() { + atomic_int x = 0; + atomic_int y = 0; + + {{{ x.store(1,mo_relaxed); + ||| { r1=x.load(mo_relaxed).readsvalue(1); + fence(mo_seq_cst); + r2=y.load(mo_relaxed); } + ||| { y.store(1,mo_relaxed); + fence(mo_seq_cst); + r3=x.load(mo_relaxed); } + }}} + return 0; + } + */ + let x = static_atomic(0); + let y = static_atomic(0); + + let j1 = spawn(move || { + x.store(1, Relaxed); + }); + + let j2 = spawn(move || { + reads_value(&x, 1); + fence(SeqCst); + y.load(Relaxed) + }); + + let j3 = spawn(move || { + y.store(1, Relaxed); + fence(SeqCst); + x.load(Relaxed) + }); + + j1.join().unwrap(); + let b = j2.join().unwrap(); + let c = j3.join().unwrap(); + + // We cannot write assert_ne!() since ui_test's fail + // tests expect exit status 1, whereas panics produce 101. + // Our ui_test does not yet support overriding failure status codes. + if (b, c) == (0, 0) { + // This *should* be unreachable, but Miri will reach it. + unsafe { + std::hint::unreachable_unchecked(); //~ERROR: unreachable + } + } +} + +pub fn main() { + for _ in 0..500 { + test_cpp20_rwc_syncs(); + } +} diff --git a/src/tools/miri/tests/fail/should-pass/cpp20_rwc_syncs.stderr b/src/tools/miri/tests/fail/should-pass/cpp20_rwc_syncs.stderr new file mode 100644 index 0000000000000..8a24b085a99f6 --- /dev/null +++ b/src/tools/miri/tests/fail/should-pass/cpp20_rwc_syncs.stderr @@ -0,0 +1,20 @@ +error: Undefined Behavior: entering unreachable code + --> $DIR/cpp20_rwc_syncs.rs:LL:CC + | +LL | std::hint::unreachable_unchecked(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ entering unreachable code + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `test_cpp20_rwc_syncs` at $DIR/cpp20_rwc_syncs.rs:LL:CC +note: inside `main` at $DIR/cpp20_rwc_syncs.rs:LL:CC + --> $DIR/cpp20_rwc_syncs.rs:LL:CC + | +LL | test_cpp20_rwc_syncs(); + | ^^^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/alias_through_mutation.rs b/src/tools/miri/tests/fail/stacked_borrows/alias_through_mutation.rs new file mode 100644 index 0000000000000..73095bb2fc94b --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/alias_through_mutation.rs @@ -0,0 +1,15 @@ +// This makes a ref that was passed to us via &mut alias with things it should not alias with +fn retarget(x: &mut &u32, target: &mut u32) { + unsafe { + *x = &mut *(target as *mut _); + } +} + +fn main() { + let target = &mut 42; + let mut target_alias = &42; // initial dummy value + retarget(&mut target_alias, target); + // now `target_alias` points to the same thing as `target` + *target = 13; + let _val = *target_alias; //~ ERROR: /read access .* tag does not exist in the borrow stack/ +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/alias_through_mutation.stderr b/src/tools/miri/tests/fail/stacked_borrows/alias_through_mutation.stderr new file mode 100644 index 0000000000000..461275c3fa346 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/alias_through_mutation.stderr @@ -0,0 +1,28 @@ +error: Undefined Behavior: attempting a read access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + --> $DIR/alias_through_mutation.rs:LL:CC + | +LL | let _val = *target_alias; + | ^^^^^^^^^^^^^ + | | + | attempting a read access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + | this error occurs as part of an access at ALLOC[0x0..0x4] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a SharedReadOnly retag at offsets [0x0..0x4] + --> $DIR/alias_through_mutation.rs:LL:CC + | +LL | *x = &mut *(target as *mut _); + | ^^^^^^^^^^^^^^^^^^^^^^^^ +help: was later invalidated at offsets [0x0..0x4] by a write access + --> $DIR/alias_through_mutation.rs:LL:CC + | +LL | *target = 13; + | ^^^^^^^^^^^^ + = note: BACKTRACE: + = note: inside `main` at $DIR/alias_through_mutation.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut1.rs b/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut1.rs new file mode 100644 index 0000000000000..14a27d8e9dd65 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut1.rs @@ -0,0 +1,14 @@ +use std::mem; + +pub fn safe(_x: &mut i32, _y: &mut i32) {} //~ ERROR: protect + +fn main() { + let mut x = 0; + let xraw: *mut i32 = unsafe { mem::transmute(&mut x) }; + // We need to apply some tricky to be able to call `safe` with two mutable references + // with the same tag: We transmute both the fn ptr (to take raw ptrs) and the argument + // (to be raw, but still have the unique tag). + let safe_raw: fn(x: *mut i32, y: *mut i32) = + unsafe { mem::transmute::(safe) }; + safe_raw(xraw, xraw); +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut1.stderr b/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut1.stderr new file mode 100644 index 0000000000000..5d4679b13ad1f --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut1.stderr @@ -0,0 +1,30 @@ +error: Undefined Behavior: not granting access to tag because that would remove [Unique for ] which is protected because it is an argument of call ID + --> $DIR/aliasing_mut1.rs:LL:CC + | +LL | pub fn safe(_x: &mut i32, _y: &mut i32) {} + | ^^ not granting access to tag because that would remove [Unique for ] which is protected because it is an argument of call ID + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a Unique retag at offsets [0x0..0x4] + --> $DIR/aliasing_mut1.rs:LL:CC + | +LL | let xraw: *mut i32 = unsafe { mem::transmute(&mut x) }; + | ^^^^^^ +help: is this argument + --> $DIR/aliasing_mut1.rs:LL:CC + | +LL | pub fn safe(_x: &mut i32, _y: &mut i32) {} + | ^^ + = note: BACKTRACE: + = note: inside `safe` at $DIR/aliasing_mut1.rs:LL:CC +note: inside `main` at $DIR/aliasing_mut1.rs:LL:CC + --> $DIR/aliasing_mut1.rs:LL:CC + | +LL | safe_raw(xraw, xraw); + | ^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut2.rs b/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut2.rs new file mode 100644 index 0000000000000..84d901f83bbcc --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut2.rs @@ -0,0 +1,14 @@ +use std::mem; + +pub fn safe(_x: &i32, _y: &mut i32) {} //~ ERROR: protect + +fn main() { + let mut x = 0; + let xref = &mut x; + let xraw: *mut i32 = unsafe { mem::transmute_copy(&xref) }; + let xshr = &*xref; + // transmute fn ptr around so that we can avoid retagging + let safe_raw: fn(x: *const i32, y: *mut i32) = + unsafe { mem::transmute::(safe) }; + safe_raw(xshr, xraw); +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut2.stderr b/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut2.stderr new file mode 100644 index 0000000000000..c8408c150e779 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut2.stderr @@ -0,0 +1,30 @@ +error: Undefined Behavior: not granting access to tag because that would remove [SharedReadOnly for ] which is protected because it is an argument of call ID + --> $DIR/aliasing_mut2.rs:LL:CC + | +LL | pub fn safe(_x: &i32, _y: &mut i32) {} + | ^^ not granting access to tag because that would remove [SharedReadOnly for ] which is protected because it is an argument of call ID + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a Unique retag at offsets [0x0..0x4] + --> $DIR/aliasing_mut2.rs:LL:CC + | +LL | let xref = &mut x; + | ^^^^^^ +help: is this argument + --> $DIR/aliasing_mut2.rs:LL:CC + | +LL | pub fn safe(_x: &i32, _y: &mut i32) {} + | ^^ + = note: BACKTRACE: + = note: inside `safe` at $DIR/aliasing_mut2.rs:LL:CC +note: inside `main` at $DIR/aliasing_mut2.rs:LL:CC + --> $DIR/aliasing_mut2.rs:LL:CC + | +LL | safe_raw(xshr, xraw); + | ^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut3.rs b/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut3.rs new file mode 100644 index 0000000000000..f1ba06b6e4f7a --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut3.rs @@ -0,0 +1,14 @@ +use std::mem; + +pub fn safe(_x: &mut i32, _y: &i32) {} //~ ERROR: borrow stack + +fn main() { + let mut x = 0; + let xref = &mut x; + let xraw: *mut i32 = unsafe { mem::transmute_copy(&xref) }; + let xshr = &*xref; + // transmute fn ptr around so that we can avoid retagging + let safe_raw: fn(x: *mut i32, y: *const i32) = + unsafe { mem::transmute::(safe) }; + safe_raw(xraw, xshr); +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut3.stderr b/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut3.stderr new file mode 100644 index 0000000000000..c2ea90f242a22 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut3.stderr @@ -0,0 +1,33 @@ +error: Undefined Behavior: trying to retag from for SharedReadOnly permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + --> $DIR/aliasing_mut3.rs:LL:CC + | +LL | pub fn safe(_x: &mut i32, _y: &i32) {} + | ^^ + | | + | trying to retag from for SharedReadOnly permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + | this error occurs as part of FnEntry retag at ALLOC[0x0..0x4] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a SharedReadOnly retag at offsets [0x0..0x4] + --> $DIR/aliasing_mut3.rs:LL:CC + | +LL | safe_raw(xraw, xshr); + | ^^^^ +help: was later invalidated at offsets [0x0..0x4] by a Unique FnEntry retag + --> $DIR/aliasing_mut3.rs:LL:CC + | +LL | safe_raw(xraw, xshr); + | ^^^^^^^^^^^^^^^^^^^^ + = note: BACKTRACE: + = note: inside `safe` at $DIR/aliasing_mut3.rs:LL:CC +note: inside `main` at $DIR/aliasing_mut3.rs:LL:CC + --> $DIR/aliasing_mut3.rs:LL:CC + | +LL | safe_raw(xraw, xshr); + | ^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut4.rs b/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut4.rs new file mode 100644 index 0000000000000..52081b56223da --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut4.rs @@ -0,0 +1,16 @@ +use std::cell::Cell; +use std::mem; + +// Make sure &mut UnsafeCell also is exclusive +pub fn safe(_x: &i32, _y: &mut Cell) {} //~ ERROR: protect + +fn main() { + let mut x = 0; + let xref = &mut x; + let xraw: *mut i32 = unsafe { mem::transmute_copy(&xref) }; + let xshr = &*xref; + // transmute fn ptr around so that we can avoid retagging + let safe_raw: fn(x: *const i32, y: *mut Cell) = + unsafe { mem::transmute::), _>(safe) }; + safe_raw(xshr, xraw as *mut _); +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut4.stderr b/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut4.stderr new file mode 100644 index 0000000000000..c53fe70f6dd33 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut4.stderr @@ -0,0 +1,30 @@ +error: Undefined Behavior: not granting access to tag because that would remove [SharedReadOnly for ] which is protected because it is an argument of call ID + --> $DIR/aliasing_mut4.rs:LL:CC + | +LL | pub fn safe(_x: &i32, _y: &mut Cell) {} + | ^^ not granting access to tag because that would remove [SharedReadOnly for ] which is protected because it is an argument of call ID + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a Unique retag at offsets [0x0..0x4] + --> $DIR/aliasing_mut4.rs:LL:CC + | +LL | let xref = &mut x; + | ^^^^^^ +help: is this argument + --> $DIR/aliasing_mut4.rs:LL:CC + | +LL | pub fn safe(_x: &i32, _y: &mut Cell) {} + | ^^ + = note: BACKTRACE: + = note: inside `safe` at $DIR/aliasing_mut4.rs:LL:CC +note: inside `main` at $DIR/aliasing_mut4.rs:LL:CC + --> $DIR/aliasing_mut4.rs:LL:CC + | +LL | safe_raw(xshr, xraw as *mut _); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/box_exclusive_violation1.rs b/src/tools/miri/tests/fail/stacked_borrows/box_exclusive_violation1.rs new file mode 100644 index 0000000000000..87a6b7bbd67ec --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/box_exclusive_violation1.rs @@ -0,0 +1,33 @@ +fn demo_box_advanced_unique(mut our: Box) -> i32 { + unknown_code_1(&*our); + + // This "re-asserts" uniqueness of the reference: After writing, we know + // our tag is at the top of the stack. + *our = 5; + + unknown_code_2(); + + // We know this will return 5 + *our +} + +// Now comes the evil context +use std::ptr; + +static mut LEAK: *mut i32 = ptr::null_mut(); + +fn unknown_code_1(x: &i32) { + unsafe { + LEAK = x as *const _ as *mut _; + } +} + +fn unknown_code_2() { + unsafe { + *LEAK = 7; //~ ERROR: /write access .* tag does not exist in the borrow stack/ + } +} + +fn main() { + demo_box_advanced_unique(Box::new(0)); +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/box_exclusive_violation1.stderr b/src/tools/miri/tests/fail/stacked_borrows/box_exclusive_violation1.stderr new file mode 100644 index 0000000000000..d82b8342f1231 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/box_exclusive_violation1.stderr @@ -0,0 +1,38 @@ +error: Undefined Behavior: attempting a write access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + --> $DIR/box_exclusive_violation1.rs:LL:CC + | +LL | *LEAK = 7; + | ^^^^^^^^^ + | | + | attempting a write access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + | this error occurs as part of an access at ALLOC[0x0..0x4] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a SharedReadOnly retag at offsets [0x0..0x4] + --> $DIR/box_exclusive_violation1.rs:LL:CC + | +LL | LEAK = x as *const _ as *mut _; + | ^ +help: was later invalidated at offsets [0x0..0x4] by a write access + --> $DIR/box_exclusive_violation1.rs:LL:CC + | +LL | *our = 5; + | ^^^^^^^^ + = note: BACKTRACE: + = note: inside `unknown_code_2` at $DIR/box_exclusive_violation1.rs:LL:CC +note: inside `demo_box_advanced_unique` at $DIR/box_exclusive_violation1.rs:LL:CC + --> $DIR/box_exclusive_violation1.rs:LL:CC + | +LL | unknown_code_2(); + | ^^^^^^^^^^^^^^^^ +note: inside `main` at $DIR/box_exclusive_violation1.rs:LL:CC + --> $DIR/box_exclusive_violation1.rs:LL:CC + | +LL | demo_box_advanced_unique(Box::new(0)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/buggy_as_mut_slice.rs b/src/tools/miri/tests/fail/stacked_borrows/buggy_as_mut_slice.rs new file mode 100644 index 0000000000000..8615a9a58ad96 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/buggy_as_mut_slice.rs @@ -0,0 +1,15 @@ +mod safe { + use std::slice::from_raw_parts_mut; + + pub fn as_mut_slice(self_: &Vec) -> &mut [T] { + unsafe { from_raw_parts_mut(self_.as_ptr() as *mut T, self_.len()) } + } +} + +fn main() { + let v = vec![0, 1, 2]; + let v1 = safe::as_mut_slice(&v); + let _v2 = safe::as_mut_slice(&v); + v1[1] = 5; + //~^ ERROR: /write access .* tag does not exist in the borrow stack/ +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/buggy_as_mut_slice.stderr b/src/tools/miri/tests/fail/stacked_borrows/buggy_as_mut_slice.stderr new file mode 100644 index 0000000000000..6aa14361287e3 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/buggy_as_mut_slice.stderr @@ -0,0 +1,28 @@ +error: Undefined Behavior: attempting a write access using at ALLOC[0x4], but that tag does not exist in the borrow stack for this location + --> $DIR/buggy_as_mut_slice.rs:LL:CC + | +LL | v1[1] = 5; + | ^^^^^^^^^ + | | + | attempting a write access using at ALLOC[0x4], but that tag does not exist in the borrow stack for this location + | this error occurs as part of an access at ALLOC[0x4..0x8] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a Unique retag at offsets [0x0..0xc] + --> $DIR/buggy_as_mut_slice.rs:LL:CC + | +LL | let v1 = safe::as_mut_slice(&v); + | ^^^^^^^^^^^^^^^^^^^^^^ +help: was later invalidated at offsets [0x0..0xc] by a Unique retag + --> $DIR/buggy_as_mut_slice.rs:LL:CC + | +LL | unsafe { from_raw_parts_mut(self_.as_ptr() as *mut T, self_.len()) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: BACKTRACE: + = note: inside `main` at $DIR/buggy_as_mut_slice.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/buggy_split_at_mut.rs b/src/tools/miri/tests/fail/stacked_borrows/buggy_split_at_mut.rs new file mode 100644 index 0000000000000..8a1ea86d63385 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/buggy_split_at_mut.rs @@ -0,0 +1,25 @@ +mod safe { + use std::slice::from_raw_parts_mut; + + pub fn split_at_mut(self_: &mut [T], mid: usize) -> (&mut [T], &mut [T]) { + let len = self_.len(); + let ptr = self_.as_mut_ptr(); + + unsafe { + assert!(mid <= len); + + ( + from_raw_parts_mut(ptr, len - mid), // BUG: should be "mid" instead of "len - mid" + from_raw_parts_mut(ptr.offset(mid as isize), len - mid), + ) + } + } +} + +fn main() { + let mut array = [1, 2, 3, 4]; + let (a, b) = safe::split_at_mut(&mut array, 0); + //~^ ERROR: /retag .* tag does not exist in the borrow stack/ + a[1] = 5; + b[1] = 6; +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/buggy_split_at_mut.stderr b/src/tools/miri/tests/fail/stacked_borrows/buggy_split_at_mut.stderr new file mode 100644 index 0000000000000..cdeccc0855a95 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/buggy_split_at_mut.stderr @@ -0,0 +1,28 @@ +error: Undefined Behavior: trying to retag from for Unique permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + --> $DIR/buggy_split_at_mut.rs:LL:CC + | +LL | let (a, b) = safe::split_at_mut(&mut array, 0); + | ^ + | | + | trying to retag from for Unique permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + | this error occurs as part of retag at ALLOC[0x0..0x10] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a Unique retag at offsets [0x0..0x10] + --> $DIR/buggy_split_at_mut.rs:LL:CC + | +LL | from_raw_parts_mut(ptr, len - mid), // BUG: should be "mid" instead of "len - mid" + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: was later invalidated at offsets [0x0..0x10] by a Unique retag + --> $DIR/buggy_split_at_mut.rs:LL:CC + | +LL | from_raw_parts_mut(ptr.offset(mid as isize), len - mid), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: BACKTRACE: + = note: inside `main` at $DIR/buggy_split_at_mut.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/deallocate_against_protector1.rs b/src/tools/miri/tests/fail/stacked_borrows/deallocate_against_protector1.rs new file mode 100644 index 0000000000000..9b710424c55c4 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/deallocate_against_protector1.rs @@ -0,0 +1,13 @@ +//@error-pattern: /deallocating while item \[Unique for .*\] is protected/ + +fn inner(x: &mut i32, f: fn(&mut i32)) { + // `f` may mutate, but it may not deallocate! + f(x) +} + +fn main() { + inner(Box::leak(Box::new(0)), |x| { + let raw = x as *mut _; + drop(unsafe { Box::from_raw(raw) }); + }); +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/deallocate_against_protector1.stderr b/src/tools/miri/tests/fail/stacked_borrows/deallocate_against_protector1.stderr new file mode 100644 index 0000000000000..a5db4a00c69e7 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/deallocate_against_protector1.stderr @@ -0,0 +1,38 @@ +error: Undefined Behavior: deallocating while item [Unique for ] is protected by call ID + --> RUSTLIB/alloc/src/alloc.rs:LL:CC + | +LL | unsafe { __rust_dealloc(ptr, layout.size(), layout.align()) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ deallocating while item [Unique for ] is protected by call ID + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information + = note: BACKTRACE: + = note: inside `std::alloc::dealloc` at RUSTLIB/alloc/src/alloc.rs:LL:CC + = note: inside `::deallocate` at RUSTLIB/alloc/src/alloc.rs:LL:CC + = note: inside `alloc::alloc::box_free::` at RUSTLIB/alloc/src/alloc.rs:LL:CC + = note: inside `std::ptr::drop_in_place::> - shim(Some(std::boxed::Box))` at RUSTLIB/core/src/ptr/mod.rs:LL:CC + = note: inside `std::mem::drop::>` at RUSTLIB/core/src/mem/mod.rs:LL:CC +note: inside closure at $DIR/deallocate_against_protector1.rs:LL:CC + --> $DIR/deallocate_against_protector1.rs:LL:CC + | +LL | drop(unsafe { Box::from_raw(raw) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: inside `<[closure@$DIR/deallocate_against_protector1.rs:LL:CC] as std::ops::FnOnce<(&mut i32,)>>::call_once - shim` at RUSTLIB/core/src/ops/function.rs:LL:CC +note: inside `inner` at $DIR/deallocate_against_protector1.rs:LL:CC + --> $DIR/deallocate_against_protector1.rs:LL:CC + | +LL | f(x) + | ^^^^ +note: inside `main` at $DIR/deallocate_against_protector1.rs:LL:CC + --> $DIR/deallocate_against_protector1.rs:LL:CC + | +LL | / inner(Box::leak(Box::new(0)), |x| { +LL | | let raw = x as *mut _; +LL | | drop(unsafe { Box::from_raw(raw) }); +LL | | }); + | |______^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/deallocate_against_protector2.rs b/src/tools/miri/tests/fail/stacked_borrows/deallocate_against_protector2.rs new file mode 100644 index 0000000000000..36e133e383650 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/deallocate_against_protector2.rs @@ -0,0 +1,16 @@ +//@error-pattern: /deallocating while item \[SharedReadWrite for .*\] is protected/ +use std::marker::PhantomPinned; + +pub struct NotUnpin(i32, PhantomPinned); + +fn inner(x: &mut NotUnpin, f: fn(&mut NotUnpin)) { + // `f` may mutate, but it may not deallocate! + f(x) +} + +fn main() { + inner(Box::leak(Box::new(NotUnpin(0, PhantomPinned))), |x| { + let raw = x as *mut _; + drop(unsafe { Box::from_raw(raw) }); + }); +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/deallocate_against_protector2.stderr b/src/tools/miri/tests/fail/stacked_borrows/deallocate_against_protector2.stderr new file mode 100644 index 0000000000000..99c6ee6eb0743 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/deallocate_against_protector2.stderr @@ -0,0 +1,38 @@ +error: Undefined Behavior: deallocating while item [SharedReadWrite for ] is protected by call ID + --> RUSTLIB/alloc/src/alloc.rs:LL:CC + | +LL | unsafe { __rust_dealloc(ptr, layout.size(), layout.align()) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ deallocating while item [SharedReadWrite for ] is protected by call ID + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information + = note: BACKTRACE: + = note: inside `std::alloc::dealloc` at RUSTLIB/alloc/src/alloc.rs:LL:CC + = note: inside `::deallocate` at RUSTLIB/alloc/src/alloc.rs:LL:CC + = note: inside `alloc::alloc::box_free::` at RUSTLIB/alloc/src/alloc.rs:LL:CC + = note: inside `std::ptr::drop_in_place::> - shim(Some(std::boxed::Box))` at RUSTLIB/core/src/ptr/mod.rs:LL:CC + = note: inside `std::mem::drop::>` at RUSTLIB/core/src/mem/mod.rs:LL:CC +note: inside closure at $DIR/deallocate_against_protector2.rs:LL:CC + --> $DIR/deallocate_against_protector2.rs:LL:CC + | +LL | drop(unsafe { Box::from_raw(raw) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: inside `<[closure@$DIR/deallocate_against_protector2.rs:LL:CC] as std::ops::FnOnce<(&mut NotUnpin,)>>::call_once - shim` at RUSTLIB/core/src/ops/function.rs:LL:CC +note: inside `inner` at $DIR/deallocate_against_protector2.rs:LL:CC + --> $DIR/deallocate_against_protector2.rs:LL:CC + | +LL | f(x) + | ^^^^ +note: inside `main` at $DIR/deallocate_against_protector2.rs:LL:CC + --> $DIR/deallocate_against_protector2.rs:LL:CC + | +LL | / inner(Box::leak(Box::new(NotUnpin(0, PhantomPinned))), |x| { +LL | | let raw = x as *mut _; +LL | | drop(unsafe { Box::from_raw(raw) }); +LL | | }); + | |______^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/disable_mut_does_not_merge_srw.rs b/src/tools/miri/tests/fail/stacked_borrows/disable_mut_does_not_merge_srw.rs new file mode 100644 index 0000000000000..fed5cd26f4d63 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/disable_mut_does_not_merge_srw.rs @@ -0,0 +1,22 @@ +// This tests demonstrates the effect of 'Disabling' mutable references on reads, rather than +// removing them from the stack -- the latter would 'merge' neighboring SRW groups which we would +// like to avoid. +fn main() { + unsafe { + let mut mem = 0; + let base = &mut mem as *mut i32; // the base pointer we build the rest of the stack on + let raw = { + let mutref = &mut *base; + mutref as *mut i32 + }; + // In the stack, we now have [base, mutref, raw]. + // We do this in a weird way where `mutref` is out of scope here, just in case + // Miri decides to get smart and argue that items for tags that are no longer + // used by any pointer stored anywhere in the machine can be removed. + let _val = *base; + // now mutref is disabled + *base = 1; + // this should pop raw from the stack, since it is in a different SRW group + let _val = *raw; //~ERROR: that tag does not exist in the borrow stack + } +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/disable_mut_does_not_merge_srw.stderr b/src/tools/miri/tests/fail/stacked_borrows/disable_mut_does_not_merge_srw.stderr new file mode 100644 index 0000000000000..e05f44fac9d2f --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/disable_mut_does_not_merge_srw.stderr @@ -0,0 +1,28 @@ +error: Undefined Behavior: attempting a read access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + --> $DIR/disable_mut_does_not_merge_srw.rs:LL:CC + | +LL | let _val = *raw; + | ^^^^ + | | + | attempting a read access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + | this error occurs as part of an access at ALLOC[0x0..0x4] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a SharedReadWrite retag at offsets [0x0..0x4] + --> $DIR/disable_mut_does_not_merge_srw.rs:LL:CC + | +LL | mutref as *mut i32 + | ^^^^^^ +help: was later invalidated at offsets [0x0..0x4] by a write access + --> $DIR/disable_mut_does_not_merge_srw.rs:LL:CC + | +LL | *base = 1; + | ^^^^^^^^^ + = note: BACKTRACE: + = note: inside `main` at $DIR/disable_mut_does_not_merge_srw.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/exposed_only_ro.rs b/src/tools/miri/tests/fail/stacked_borrows/exposed_only_ro.rs new file mode 100644 index 0000000000000..0b4fb0ccd33bd --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/exposed_only_ro.rs @@ -0,0 +1,12 @@ +//@compile-flags: -Zmiri-permissive-provenance +#![feature(strict_provenance)] + +// If we have only exposed read-only pointers, doing a write through a wildcard ptr should fail. + +fn main() { + let mut x = 0; + let _fool = &mut x as *mut i32; // this would have fooled the old untagged pointer logic + let addr = (&x as *const i32).expose_addr(); + let ptr = std::ptr::from_exposed_addr_mut::(addr); + unsafe { *ptr = 0 }; //~ ERROR: /write access using .* no exposed tags have suitable permission in the borrow stack/ +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/exposed_only_ro.stderr b/src/tools/miri/tests/fail/stacked_borrows/exposed_only_ro.stderr new file mode 100644 index 0000000000000..cb5e7bffde480 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/exposed_only_ro.stderr @@ -0,0 +1,18 @@ +error: Undefined Behavior: attempting a write access using at ALLOC[0x0], but no exposed tags have suitable permission in the borrow stack for this location + --> $DIR/exposed_only_ro.rs:LL:CC + | +LL | unsafe { *ptr = 0 }; + | ^^^^^^^^ + | | + | attempting a write access using at ALLOC[0x0], but no exposed tags have suitable permission in the borrow stack for this location + | this error occurs as part of an access at ALLOC[0x0..0x4] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/exposed_only_ro.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/fnentry_invalidation.rs b/src/tools/miri/tests/fail/stacked_borrows/fnentry_invalidation.rs new file mode 100644 index 0000000000000..37214bebb8285 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/fnentry_invalidation.rs @@ -0,0 +1,20 @@ +// Test that spans displayed in diagnostics identify the function call, not the function +// definition, as the location of invalidation due to FnEntry retag. Technically the FnEntry retag +// occurs inside the function, but what the user wants to know is which call produced the +// invalidation. +fn main() { + let mut x = 0i32; + let z = &mut x as *mut i32; + x.do_bad(); + unsafe { + let _oof = *z; //~ ERROR: /read access .* tag does not exist in the borrow stack/ + } +} + +trait Bad { + fn do_bad(&mut self) { + // who knows + } +} + +impl Bad for i32 {} diff --git a/src/tools/miri/tests/fail/stacked_borrows/fnentry_invalidation.stderr b/src/tools/miri/tests/fail/stacked_borrows/fnentry_invalidation.stderr new file mode 100644 index 0000000000000..653ceca858859 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/fnentry_invalidation.stderr @@ -0,0 +1,28 @@ +error: Undefined Behavior: attempting a read access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + --> $DIR/fnentry_invalidation.rs:LL:CC + | +LL | let _oof = *z; + | ^^ + | | + | attempting a read access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + | this error occurs as part of an access at ALLOC[0x0..0x4] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a SharedReadWrite retag at offsets [0x0..0x4] + --> $DIR/fnentry_invalidation.rs:LL:CC + | +LL | let z = &mut x as *mut i32; + | ^^^^^^ +help: was later invalidated at offsets [0x0..0x4] by a Unique FnEntry retag + --> $DIR/fnentry_invalidation.rs:LL:CC + | +LL | x.do_bad(); + | ^^^^^^^^^^ + = note: BACKTRACE: + = note: inside `main` at $DIR/fnentry_invalidation.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_read1.rs b/src/tools/miri/tests/fail/stacked_borrows/illegal_read1.rs new file mode 100644 index 0000000000000..1dea282739ce1 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_read1.rs @@ -0,0 +1,16 @@ +// A callee may not read the destination of our `&mut` without +// us noticing. + +#[rustfmt::skip] // rustfmt bug: https://github.com/rust-lang/rustfmt/issues/5391 +fn main() { + let mut x = 15; + let xraw = &mut x as *mut _; + let xref = unsafe { &mut *xraw }; // derived from raw, so using raw is still ok... + callee(xraw); + let _val = *xref; // ...but any use of raw will invalidate our ref. + //~^ ERROR: /read access .* tag does not exist in the borrow stack/ +} + +fn callee(xraw: *mut i32) { + let _val = unsafe { *xraw }; +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_read1.stderr b/src/tools/miri/tests/fail/stacked_borrows/illegal_read1.stderr new file mode 100644 index 0000000000000..95ff05d70c30e --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_read1.stderr @@ -0,0 +1,28 @@ +error: Undefined Behavior: attempting a read access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + --> $DIR/illegal_read1.rs:LL:CC + | +LL | let _val = *xref; // ...but any use of raw will invalidate our ref. + | ^^^^^ + | | + | attempting a read access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + | this error occurs as part of an access at ALLOC[0x0..0x4] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a Unique retag at offsets [0x0..0x4] + --> $DIR/illegal_read1.rs:LL:CC + | +LL | let xref = unsafe { &mut *xraw }; // derived from raw, so using raw is still ok... + | ^^^^^^^^^^ +help: was later invalidated at offsets [0x0..0x4] by a read access + --> $DIR/illegal_read1.rs:LL:CC + | +LL | let _val = unsafe { *xraw }; + | ^^^^^ + = note: BACKTRACE: + = note: inside `main` at $DIR/illegal_read1.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_read2.rs b/src/tools/miri/tests/fail/stacked_borrows/illegal_read2.rs new file mode 100644 index 0000000000000..b765b4d9ce99d --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_read2.rs @@ -0,0 +1,19 @@ +// A callee may not read the destination of our `&mut` without +// us noticing. + +#[rustfmt::skip] // rustfmt bug: https://github.com/rust-lang/rustfmt/issues/5391 +fn main() { + let mut x = 15; + let xraw = &mut x as *mut _; + let xref = unsafe { &mut *xraw }; // derived from raw, so using raw is still ok... + callee(xraw); + let _val = *xref; // ...but any use of raw will invalidate our ref. + //~^ ERROR: /read access .* tag does not exist in the borrow stack/ +} + +fn callee(xraw: *mut i32) { + // We are a bit sneaky: We first create a shared ref, exploiting the reborrowing rules, + // and then we read through that. + let shr = unsafe { &*xraw }; + let _val = *shr; +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_read2.stderr b/src/tools/miri/tests/fail/stacked_borrows/illegal_read2.stderr new file mode 100644 index 0000000000000..5cfdf77dee402 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_read2.stderr @@ -0,0 +1,28 @@ +error: Undefined Behavior: attempting a read access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + --> $DIR/illegal_read2.rs:LL:CC + | +LL | let _val = *xref; // ...but any use of raw will invalidate our ref. + | ^^^^^ + | | + | attempting a read access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + | this error occurs as part of an access at ALLOC[0x0..0x4] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a Unique retag at offsets [0x0..0x4] + --> $DIR/illegal_read2.rs:LL:CC + | +LL | let xref = unsafe { &mut *xraw }; // derived from raw, so using raw is still ok... + | ^^^^^^^^^^ +help: was later invalidated at offsets [0x0..0x4] by a SharedReadOnly retag + --> $DIR/illegal_read2.rs:LL:CC + | +LL | let shr = unsafe { &*xraw }; + | ^^^^^^ + = note: BACKTRACE: + = note: inside `main` at $DIR/illegal_read2.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_read3.rs b/src/tools/miri/tests/fail/stacked_borrows/illegal_read3.rs new file mode 100644 index 0000000000000..43ea0a0e84da5 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_read3.rs @@ -0,0 +1,28 @@ +// A callee may not read the destination of our `&mut` without us noticing. +// Thise code got carefully checked to not introduce any reborrows +// that are not explicit in the source. Let's hope the compiler does not break this later! + +use std::mem; + +union HiddenRef { + // We avoid retagging at this type, and we only read, so shared vs mutable does not matter. + r: &'static i32, +} + +fn main() { + let mut x: i32 = 15; + let xref1 = &mut x; + let xref1_sneaky: HiddenRef = unsafe { mem::transmute_copy(&xref1) }; + // Derived from `xref1`, so using raw value is still ok, ... + let xref2 = &mut *xref1; + callee(xref1_sneaky); + // ... though any use of it will invalidate our ref. + let _val = *xref2; + //~^ ERROR: /read access .* tag does not exist in the borrow stack/ +} + +fn callee(xref1: HiddenRef) { + // Doing the deref and the transmute (through the union) in the same place expression + // should avoid retagging. + let _val = unsafe { *xref1.r }; +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_read3.stderr b/src/tools/miri/tests/fail/stacked_borrows/illegal_read3.stderr new file mode 100644 index 0000000000000..dacf71fa3ee39 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_read3.stderr @@ -0,0 +1,28 @@ +error: Undefined Behavior: attempting a read access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + --> $DIR/illegal_read3.rs:LL:CC + | +LL | let _val = *xref2; + | ^^^^^^ + | | + | attempting a read access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + | this error occurs as part of an access at ALLOC[0x0..0x4] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a Unique retag at offsets [0x0..0x4] + --> $DIR/illegal_read3.rs:LL:CC + | +LL | let xref2 = &mut *xref1; + | ^^^^^^^^^^^ +help: was later invalidated at offsets [0x0..0x4] by a read access + --> $DIR/illegal_read3.rs:LL:CC + | +LL | let _val = unsafe { *xref1.r }; + | ^^^^^^^^ + = note: BACKTRACE: + = note: inside `main` at $DIR/illegal_read3.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_read4.rs b/src/tools/miri/tests/fail/stacked_borrows/illegal_read4.rs new file mode 100644 index 0000000000000..a9ecb88d3520b --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_read4.rs @@ -0,0 +1,9 @@ +// Using a raw invalidates derived `&mut` even for reading. +fn main() { + let mut x = 2; + let xref1 = &mut x; + let xraw = xref1 as *mut _; + let xref2 = unsafe { &mut *xraw }; + let _val = unsafe { *xraw }; // use the raw again, this invalidates xref2 *even* with the special read except for uniq refs + let _illegal = *xref2; //~ ERROR: /read access .* tag does not exist in the borrow stack/ +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_read4.stderr b/src/tools/miri/tests/fail/stacked_borrows/illegal_read4.stderr new file mode 100644 index 0000000000000..5ce0cba617914 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_read4.stderr @@ -0,0 +1,28 @@ +error: Undefined Behavior: attempting a read access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + --> $DIR/illegal_read4.rs:LL:CC + | +LL | let _illegal = *xref2; + | ^^^^^^ + | | + | attempting a read access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + | this error occurs as part of an access at ALLOC[0x0..0x4] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a Unique retag at offsets [0x0..0x4] + --> $DIR/illegal_read4.rs:LL:CC + | +LL | let xref2 = unsafe { &mut *xraw }; + | ^^^^^^^^^^ +help: was later invalidated at offsets [0x0..0x4] by a read access + --> $DIR/illegal_read4.rs:LL:CC + | +LL | let _val = unsafe { *xraw }; // use the raw again, this invalidates xref2 *even* with the special read except for uniq refs + | ^^^^^ + = note: BACKTRACE: + = note: inside `main` at $DIR/illegal_read4.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_read5.rs b/src/tools/miri/tests/fail/stacked_borrows/illegal_read5.rs new file mode 100644 index 0000000000000..228c15f72e173 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_read5.rs @@ -0,0 +1,18 @@ +// We *can* have aliasing &RefCell and &mut T, but we cannot read through the former. +// Else we couldn't optimize based on the assumption that `xref` below is truly unique. +//@normalize-stderr-test: "0x[0-9a-fA-F]+" -> "$$HEX" + +use std::cell::RefCell; +use std::{mem, ptr}; + +#[rustfmt::skip] // rustfmt bug: https://github.com/rust-lang/rustfmt/issues/5391 +fn main() { + let rc = RefCell::new(0); + let mut refmut = rc.borrow_mut(); + let xref: &mut i32 = &mut *refmut; + let xshr = &rc; // creating this is ok + let _val = *xref; // we can even still use our mutable reference + mem::forget(unsafe { ptr::read(xshr) }); // but after reading through the shared ref + let _val = *xref; // the mutable one is dead and gone + //~^ ERROR: /read access .* tag does not exist in the borrow stack/ +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_read5.stderr b/src/tools/miri/tests/fail/stacked_borrows/illegal_read5.stderr new file mode 100644 index 0000000000000..63532f87944eb --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_read5.stderr @@ -0,0 +1,28 @@ +error: Undefined Behavior: attempting a read access using at ALLOC[$HEX], but that tag does not exist in the borrow stack for this location + --> $DIR/illegal_read5.rs:LL:CC + | +LL | let _val = *xref; // the mutable one is dead and gone + | ^^^^^ + | | + | attempting a read access using at ALLOC[$HEX], but that tag does not exist in the borrow stack for this location + | this error occurs as part of an access at ALLOC[$HEX..$HEX] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a Unique retag at offsets [$HEX..$HEX] + --> $DIR/illegal_read5.rs:LL:CC + | +LL | let xref: &mut i32 = &mut *refmut; + | ^^^^^^^^^^^^ +help: was later invalidated at offsets [$HEX..$HEX] by a read access + --> $DIR/illegal_read5.rs:LL:CC + | +LL | mem::forget(unsafe { ptr::read(xshr) }); // but after reading through the shared ref + | ^^^^^^^^^^^^^^^ + = note: BACKTRACE: + = note: inside `main` at $DIR/illegal_read5.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_read6.rs b/src/tools/miri/tests/fail/stacked_borrows/illegal_read6.rs new file mode 100644 index 0000000000000..4af22580ab64f --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_read6.rs @@ -0,0 +1,10 @@ +// Creating a shared reference does not leak the data to raw pointers. +fn main() { + unsafe { + let x = &mut 0; + let raw = x as *mut _; + let x = &mut *x; // kill `raw` + let _y = &*x; // this should not activate `raw` again + let _val = *raw; //~ ERROR: /read access .* tag does not exist in the borrow stack/ + } +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_read6.stderr b/src/tools/miri/tests/fail/stacked_borrows/illegal_read6.stderr new file mode 100644 index 0000000000000..93a96ab601ea3 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_read6.stderr @@ -0,0 +1,28 @@ +error: Undefined Behavior: attempting a read access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + --> $DIR/illegal_read6.rs:LL:CC + | +LL | let _val = *raw; + | ^^^^ + | | + | attempting a read access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + | this error occurs as part of an access at ALLOC[0x0..0x4] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a SharedReadWrite retag at offsets [0x0..0x4] + --> $DIR/illegal_read6.rs:LL:CC + | +LL | let raw = x as *mut _; + | ^ +help: was later invalidated at offsets [0x0..0x4] by a Unique retag + --> $DIR/illegal_read6.rs:LL:CC + | +LL | let x = &mut *x; // kill `raw` + | ^^^^^^^ + = note: BACKTRACE: + = note: inside `main` at $DIR/illegal_read6.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_read7.rs b/src/tools/miri/tests/fail/stacked_borrows/illegal_read7.rs new file mode 100644 index 0000000000000..1901e8e4e3480 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_read7.rs @@ -0,0 +1,22 @@ +// Creating a shared reference does not leak the data to raw pointers, +// not even when interior mutability is involved. + +use std::cell::Cell; +use std::ptr; + +fn main() { + unsafe { + let x = &mut Cell::new(0); + let raw = x as *mut Cell; + let x = &mut *raw; + let _shr = &*x; + // The state here is interesting because the top of the stack is [Unique, SharedReadWrite], + // just like if we had done `x as *mut _`. + // If we said that reading from a lower item is fine if the top item is `SharedReadWrite` + // (one way to maybe preserve a stack discipline), then we could now read from `raw` + // without invalidating `x`. That would be bad! It would mean that creating `shr` + // leaked `x` to `raw`. + let _val = ptr::read(raw); + let _val = *x.get_mut(); //~ ERROR: /retag .* tag does not exist in the borrow stack/ + } +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_read7.stderr b/src/tools/miri/tests/fail/stacked_borrows/illegal_read7.stderr new file mode 100644 index 0000000000000..2e8ac207beafb --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_read7.stderr @@ -0,0 +1,28 @@ +error: Undefined Behavior: trying to retag from for SharedReadWrite permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + --> $DIR/illegal_read7.rs:LL:CC + | +LL | let _val = *x.get_mut(); + | ^^^^^^^^^^^ + | | + | trying to retag from for SharedReadWrite permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + | this error occurs as part of two-phase retag at ALLOC[0x0..0x4] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a Unique retag at offsets [0x0..0x4] + --> $DIR/illegal_read7.rs:LL:CC + | +LL | let x = &mut *raw; + | ^^^^^^^^^ +help: was later invalidated at offsets [0x0..0x4] by a read access + --> $DIR/illegal_read7.rs:LL:CC + | +LL | let _val = ptr::read(raw); + | ^^^^^^^^^^^^^^ + = note: BACKTRACE: + = note: inside `main` at $DIR/illegal_read7.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_read8.rs b/src/tools/miri/tests/fail/stacked_borrows/illegal_read8.rs new file mode 100644 index 0000000000000..f0a1658a5a23a --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_read8.rs @@ -0,0 +1,15 @@ +// Make sure that creating a raw ptr next to a shared ref works +// but the shared ref still gets invalidated when the raw ptr is used for writing. + +fn main() { + unsafe { + use std::mem; + let x = &mut 0; + let y1: &i32 = mem::transmute(&*x); // launder lifetimes + let y2 = x as *mut _; + let _val = *y2; + let _val = *y1; + *y2 += 1; + let _fail = *y1; //~ ERROR: /read access .* tag does not exist in the borrow stack/ + } +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_read8.stderr b/src/tools/miri/tests/fail/stacked_borrows/illegal_read8.stderr new file mode 100644 index 0000000000000..c34fa2d8955dd --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_read8.stderr @@ -0,0 +1,28 @@ +error: Undefined Behavior: attempting a read access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + --> $DIR/illegal_read8.rs:LL:CC + | +LL | let _fail = *y1; + | ^^^ + | | + | attempting a read access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + | this error occurs as part of an access at ALLOC[0x0..0x4] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a SharedReadOnly retag at offsets [0x0..0x4] + --> $DIR/illegal_read8.rs:LL:CC + | +LL | let y1: &i32 = mem::transmute(&*x); // launder lifetimes + | ^^^^^^^^^^^^^^^^^^^ +help: was later invalidated at offsets [0x0..0x4] by a write access + --> $DIR/illegal_read8.rs:LL:CC + | +LL | *y2 += 1; + | ^^^^^^^^ + = note: BACKTRACE: + = note: inside `main` at $DIR/illegal_read8.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_read_despite_exposed1.rs b/src/tools/miri/tests/fail/stacked_borrows/illegal_read_despite_exposed1.rs new file mode 100644 index 0000000000000..76516b7d924be --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_read_despite_exposed1.rs @@ -0,0 +1,17 @@ +//@compile-flags: -Zmiri-permissive-provenance + +fn main() { + unsafe { + let root = &mut 42; + let addr = root as *mut i32 as usize; + let exposed_ptr = addr as *mut i32; + // From the exposed ptr, we get a new unique ptr. + let root2 = &mut *exposed_ptr; + let _fool = root2 as *mut _; // this would have fooled the old untagged pointer logic + // Stack: Unknown( at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + --> $DIR/illegal_read_despite_exposed1.rs:LL:CC + | +LL | let _val = *root2; + | ^^^^^^ + | | + | attempting a read access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + | this error occurs as part of an access at ALLOC[0x0..0x4] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a Unique retag at offsets [0x0..0x4] + --> $DIR/illegal_read_despite_exposed1.rs:LL:CC + | +LL | let root2 = &mut *exposed_ptr; + | ^^^^^^^^^^^^^^^^^ +help: was later invalidated at offsets [0x0..0x4] by a write access + --> $DIR/illegal_read_despite_exposed1.rs:LL:CC + | +LL | *exposed_ptr = 0; + | ^^^^^^^^^^^^^^^^ + = note: BACKTRACE: + = note: inside `main` at $DIR/illegal_read_despite_exposed1.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_read_despite_exposed2.rs b/src/tools/miri/tests/fail/stacked_borrows/illegal_read_despite_exposed2.rs new file mode 100644 index 0000000000000..97e0bf40c0dd3 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_read_despite_exposed2.rs @@ -0,0 +1,20 @@ +//@compile-flags: -Zmiri-permissive-provenance + +fn main() { + unsafe { + let root = &mut 42; + let addr = root as *mut i32 as usize; + let exposed_ptr = addr as *mut i32; + // From the exposed ptr, we get a new unique ptr. + let root2 = &mut *exposed_ptr; + // let _fool = root2 as *mut _; // this would fool us, since SRW(N+1) remains on the stack + // Stack: Unknown( at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + --> $DIR/illegal_read_despite_exposed2.rs:LL:CC + | +LL | let _val = *root2; + | ^^^^^^ + | | + | attempting a read access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + | this error occurs as part of an access at ALLOC[0x0..0x4] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a Unique retag at offsets [0x0..0x4] + --> $DIR/illegal_read_despite_exposed2.rs:LL:CC + | +LL | let root2 = &mut *exposed_ptr; + | ^^^^^^^^^^^^^^^^^ +help: was later invalidated at offsets [0x0..0x4] by a read access + --> $DIR/illegal_read_despite_exposed2.rs:LL:CC + | +LL | let _val = *exposed_ptr; + | ^^^^^^^^^^^^ + = note: BACKTRACE: + = note: inside `main` at $DIR/illegal_read_despite_exposed2.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_write1.rs b/src/tools/miri/tests/fail/stacked_borrows/illegal_write1.rs new file mode 100644 index 0000000000000..f28401938a925 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_write1.rs @@ -0,0 +1,9 @@ +fn main() { + let target = Box::new(42); // has an implicit raw + let xref = &*target; + { + let x: *mut u32 = xref as *const _ as *mut _; + unsafe { *x = 42 }; //~ ERROR: /write access .* tag only grants SharedReadOnly permission/ + } + let _x = *xref; +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_write1.stderr b/src/tools/miri/tests/fail/stacked_borrows/illegal_write1.stderr new file mode 100644 index 0000000000000..3bf27f4815e9a --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_write1.stderr @@ -0,0 +1,23 @@ +error: Undefined Behavior: attempting a write access using at ALLOC[0x0], but that tag only grants SharedReadOnly permission for this location + --> $DIR/illegal_write1.rs:LL:CC + | +LL | unsafe { *x = 42 }; + | ^^^^^^^ + | | + | attempting a write access using at ALLOC[0x0], but that tag only grants SharedReadOnly permission for this location + | this error occurs as part of an access at ALLOC[0x0..0x4] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a SharedReadOnly retag at offsets [0x0..0x4] + --> $DIR/illegal_write1.rs:LL:CC + | +LL | let x: *mut u32 = xref as *const _ as *mut _; + | ^^^^ + = note: BACKTRACE: + = note: inside `main` at $DIR/illegal_write1.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_write2.rs b/src/tools/miri/tests/fail/stacked_borrows/illegal_write2.rs new file mode 100644 index 0000000000000..70c51e671fe84 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_write2.rs @@ -0,0 +1,8 @@ +fn main() { + let target = &mut 42; + let target2 = target as *mut _; + drop(&mut *target); // reborrow + // Now make sure our ref is still the only one. + unsafe { *target2 = 13 }; //~ ERROR: /write access .* tag does not exist in the borrow stack/ + let _val = *target; +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_write2.stderr b/src/tools/miri/tests/fail/stacked_borrows/illegal_write2.stderr new file mode 100644 index 0000000000000..a9fe8cb6ccc02 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_write2.stderr @@ -0,0 +1,28 @@ +error: Undefined Behavior: attempting a write access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + --> $DIR/illegal_write2.rs:LL:CC + | +LL | unsafe { *target2 = 13 }; + | ^^^^^^^^^^^^^ + | | + | attempting a write access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + | this error occurs as part of an access at ALLOC[0x0..0x4] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a SharedReadWrite retag at offsets [0x0..0x4] + --> $DIR/illegal_write2.rs:LL:CC + | +LL | let target2 = target as *mut _; + | ^^^^^^ +help: was later invalidated at offsets [0x0..0x4] by a Unique retag + --> $DIR/illegal_write2.rs:LL:CC + | +LL | drop(&mut *target); // reborrow + | ^^^^^^^^^^^^ + = note: BACKTRACE: + = note: inside `main` at $DIR/illegal_write2.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_write3.rs b/src/tools/miri/tests/fail/stacked_borrows/illegal_write3.rs new file mode 100644 index 0000000000000..6f55b63cb5c64 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_write3.rs @@ -0,0 +1,8 @@ +fn main() { + let target = 42; + // Make sure raw ptr with raw tag cannot mutate frozen location without breaking the shared ref. + let r#ref = ⌖ // freeze + let ptr = r#ref as *const _ as *mut _; // raw ptr, with raw tag + unsafe { *ptr = 42 }; //~ ERROR: /write access .* only grants SharedReadOnly permission/ + let _val = *r#ref; +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_write3.stderr b/src/tools/miri/tests/fail/stacked_borrows/illegal_write3.stderr new file mode 100644 index 0000000000000..d64f2ddd87670 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_write3.stderr @@ -0,0 +1,23 @@ +error: Undefined Behavior: attempting a write access using at ALLOC[0x0], but that tag only grants SharedReadOnly permission for this location + --> $DIR/illegal_write3.rs:LL:CC + | +LL | unsafe { *ptr = 42 }; + | ^^^^^^^^^ + | | + | attempting a write access using at ALLOC[0x0], but that tag only grants SharedReadOnly permission for this location + | this error occurs as part of an access at ALLOC[0x0..0x4] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a SharedReadOnly retag at offsets [0x0..0x4] + --> $DIR/illegal_write3.rs:LL:CC + | +LL | let ptr = r#ref as *const _ as *mut _; // raw ptr, with raw tag + | ^^^^^ + = note: BACKTRACE: + = note: inside `main` at $DIR/illegal_write3.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_write4.rs b/src/tools/miri/tests/fail/stacked_borrows/illegal_write4.rs new file mode 100644 index 0000000000000..4c5d5a4f66577 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_write4.rs @@ -0,0 +1,13 @@ +use std::mem; + +fn main() { + let mut target = 42; + // Make sure we cannot use a raw-tagged `&mut` pointing to a frozen location. + // Even just creating it unfreezes. + let raw = &mut target as *mut _; // let this leak to raw + let reference = unsafe { &*raw }; // freeze + let _ptr = reference as *const _ as *mut i32; // raw ptr, with raw tag + let _mut_ref: &mut i32 = unsafe { mem::transmute(raw) }; // &mut, with raw tag + // Now we retag, making our ref top-of-stack -- and, in particular, unfreezing. + let _val = *reference; //~ ERROR: /read access .* tag does not exist in the borrow stack/ +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_write4.stderr b/src/tools/miri/tests/fail/stacked_borrows/illegal_write4.stderr new file mode 100644 index 0000000000000..e3b8621eb74f2 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_write4.stderr @@ -0,0 +1,28 @@ +error: Undefined Behavior: attempting a read access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + --> $DIR/illegal_write4.rs:LL:CC + | +LL | let _val = *reference; + | ^^^^^^^^^^ + | | + | attempting a read access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + | this error occurs as part of an access at ALLOC[0x0..0x4] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a SharedReadOnly retag at offsets [0x0..0x4] + --> $DIR/illegal_write4.rs:LL:CC + | +LL | let reference = unsafe { &*raw }; // freeze + | ^^^^^ +help: was later invalidated at offsets [0x0..0x4] by a Unique retag + --> $DIR/illegal_write4.rs:LL:CC + | +LL | let _mut_ref: &mut i32 = unsafe { mem::transmute(raw) }; // &mut, with raw tag + | ^^^^^^^^^^^^^^^^^^^ + = note: BACKTRACE: + = note: inside `main` at $DIR/illegal_write4.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_write5.rs b/src/tools/miri/tests/fail/stacked_borrows/illegal_write5.rs new file mode 100644 index 0000000000000..1c655dc0a0fb4 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_write5.rs @@ -0,0 +1,16 @@ +// A callee may not write to the destination of our `&mut` without us noticing. + +fn main() { + let mut x = 15; + let xraw = &mut x as *mut _; + // Derived from raw value, so using raw value is still ok ... + let xref = unsafe { &mut *xraw }; + callee(xraw); + // ... though any use of raw value will invalidate our ref. + let _val = *xref; + //~^ ERROR: /read access .* tag does not exist in the borrow stack/ +} + +fn callee(xraw: *mut i32) { + unsafe { *xraw = 15 }; +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_write5.stderr b/src/tools/miri/tests/fail/stacked_borrows/illegal_write5.stderr new file mode 100644 index 0000000000000..bbeb81258bde6 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_write5.stderr @@ -0,0 +1,28 @@ +error: Undefined Behavior: attempting a read access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + --> $DIR/illegal_write5.rs:LL:CC + | +LL | let _val = *xref; + | ^^^^^ + | | + | attempting a read access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + | this error occurs as part of an access at ALLOC[0x0..0x4] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a Unique retag at offsets [0x0..0x4] + --> $DIR/illegal_write5.rs:LL:CC + | +LL | let xref = unsafe { &mut *xraw }; + | ^^^^^^^^^^ +help: was later invalidated at offsets [0x0..0x4] by a write access + --> $DIR/illegal_write5.rs:LL:CC + | +LL | unsafe { *xraw = 15 }; + | ^^^^^^^^^^ + = note: BACKTRACE: + = note: inside `main` at $DIR/illegal_write5.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_write6.rs b/src/tools/miri/tests/fail/stacked_borrows/illegal_write6.rs new file mode 100644 index 0000000000000..448f1493367af --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_write6.rs @@ -0,0 +1,12 @@ +fn main() { + let x = &mut 0u32; + let p = x as *mut u32; + foo(x, p); +} + +fn foo(a: &mut u32, y: *mut u32) -> u32 { + *a = 1; + let _b = &*a; + unsafe { *y = 2 }; //~ ERROR: /not granting access .* because that would remove .* which is protected/ + return *a; +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_write6.stderr b/src/tools/miri/tests/fail/stacked_borrows/illegal_write6.stderr new file mode 100644 index 0000000000000..331faa89f8604 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_write6.stderr @@ -0,0 +1,30 @@ +error: Undefined Behavior: not granting access to tag because that would remove [Unique for ] which is protected because it is an argument of call ID + --> $DIR/illegal_write6.rs:LL:CC + | +LL | unsafe { *y = 2 }; + | ^^^^^^ not granting access to tag because that would remove [Unique for ] which is protected because it is an argument of call ID + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a SharedReadWrite retag at offsets [0x0..0x4] + --> $DIR/illegal_write6.rs:LL:CC + | +LL | let p = x as *mut u32; + | ^ +help: is this argument + --> $DIR/illegal_write6.rs:LL:CC + | +LL | fn foo(a: &mut u32, y: *mut u32) -> u32 { + | ^ + = note: BACKTRACE: + = note: inside `foo` at $DIR/illegal_write6.rs:LL:CC +note: inside `main` at $DIR/illegal_write6.rs:LL:CC + --> $DIR/illegal_write6.rs:LL:CC + | +LL | foo(x, p); + | ^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_write_despite_exposed1.rs b/src/tools/miri/tests/fail/stacked_borrows/illegal_write_despite_exposed1.rs new file mode 100644 index 0000000000000..0e34c5c98fc1f --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_write_despite_exposed1.rs @@ -0,0 +1,17 @@ +//@compile-flags: -Zmiri-permissive-provenance + +fn main() { + unsafe { + let root = &mut 42; + let addr = root as *mut i32 as usize; + let exposed_ptr = addr as *mut i32; + // From the exposed ptr, we get a new SRO ptr. + let root2 = &*exposed_ptr; + // Stack: Unknown( at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + --> $DIR/illegal_write_despite_exposed1.rs:LL:CC + | +LL | let _val = *root2; + | ^^^^^^ + | | + | attempting a read access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + | this error occurs as part of an access at ALLOC[0x0..0x4] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a SharedReadOnly retag at offsets [0x0..0x4] + --> $DIR/illegal_write_despite_exposed1.rs:LL:CC + | +LL | let root2 = &*exposed_ptr; + | ^^^^^^^^^^^^^ +help: was later invalidated at offsets [0x0..0x4] by a write access + --> $DIR/illegal_write_despite_exposed1.rs:LL:CC + | +LL | *exposed_ptr = 0; + | ^^^^^^^^^^^^^^^^ + = note: BACKTRACE: + = note: inside `main` at $DIR/illegal_write_despite_exposed1.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/interior_mut1.rs b/src/tools/miri/tests/fail/stacked_borrows/interior_mut1.rs new file mode 100644 index 0000000000000..6911238fc53e9 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/interior_mut1.rs @@ -0,0 +1,17 @@ +use std::cell::UnsafeCell; + +fn main() { + unsafe { + let c = &UnsafeCell::new(UnsafeCell::new(0)); + let inner_uniq = &mut *c.get(); + // stack: [c: SharedReadWrite, inner_uniq: Unique] + + let inner_shr = &*inner_uniq; // adds a SharedReadWrite + // stack: [c: SharedReadWrite, inner_uniq: Unique, inner_shr: SharedReadWrite] + + *c.get() = UnsafeCell::new(1); // invalidates inner_shr + // stack: [c: SharedReadWrite] + + let _val = *inner_shr.get(); //~ ERROR: /retag .* tag does not exist in the borrow stack/ + } +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/interior_mut1.stderr b/src/tools/miri/tests/fail/stacked_borrows/interior_mut1.stderr new file mode 100644 index 0000000000000..1d68727c82af4 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/interior_mut1.stderr @@ -0,0 +1,28 @@ +error: Undefined Behavior: trying to retag from for SharedReadWrite permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + --> $DIR/interior_mut1.rs:LL:CC + | +LL | let _val = *inner_shr.get(); + | ^^^^^^^^^^^^^^^ + | | + | trying to retag from for SharedReadWrite permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + | this error occurs as part of retag at ALLOC[0x0..0x4] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a SharedReadWrite retag at offsets [0x0..0x4] + --> $DIR/interior_mut1.rs:LL:CC + | +LL | let inner_shr = &*inner_uniq; // adds a SharedReadWrite + | ^^^^^^^^^^^^ +help: was later invalidated at offsets [0x0..0x4] by a write access + --> $DIR/interior_mut1.rs:LL:CC + | +LL | *c.get() = UnsafeCell::new(1); // invalidates inner_shr + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: BACKTRACE: + = note: inside `main` at $DIR/interior_mut1.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/interior_mut2.rs b/src/tools/miri/tests/fail/stacked_borrows/interior_mut2.rs new file mode 100644 index 0000000000000..5e9d177cd038d --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/interior_mut2.rs @@ -0,0 +1,30 @@ +use std::cell::UnsafeCell; +use std::mem; + +// Like `&mut *x.get()`, but without intermediate raw pointers. +#[allow(mutable_transmutes)] +unsafe fn unsafe_cell_get(x: &UnsafeCell) -> &'static mut T { + mem::transmute(x) +} + +fn main() { + unsafe { + let c = &UnsafeCell::new(UnsafeCell::new(0)); + let inner_uniq = &mut *c.get(); + let inner_shr = &*inner_uniq; + // stack: [c: SharedReadWrite, inner_uniq: Unique, inner_shr: SharedReadWrite] + + let _val = c.get().read(); // invalidates inner_uniq + // stack: [c: SharedReadWrite, inner_uniq: Disabled, inner_shr: SharedReadWrite] + + // We have to be careful not to add any raw pointers above inner_uniq in + // the stack, hence the use of unsafe_cell_get. + let _val = *unsafe_cell_get(inner_shr); // this still works + + *c.get() = UnsafeCell::new(0); // now inner_shr gets invalidated + // stack: [c: SharedReadWrite] + + // now this does not work any more + let _val = *inner_shr.get(); //~ ERROR: /retag .* tag does not exist in the borrow stack/ + } +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/interior_mut2.stderr b/src/tools/miri/tests/fail/stacked_borrows/interior_mut2.stderr new file mode 100644 index 0000000000000..8a3357142261b --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/interior_mut2.stderr @@ -0,0 +1,28 @@ +error: Undefined Behavior: trying to retag from for SharedReadWrite permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + --> $DIR/interior_mut2.rs:LL:CC + | +LL | let _val = *inner_shr.get(); + | ^^^^^^^^^^^^^^^ + | | + | trying to retag from for SharedReadWrite permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + | this error occurs as part of retag at ALLOC[0x0..0x4] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a SharedReadWrite retag at offsets [0x0..0x4] + --> $DIR/interior_mut2.rs:LL:CC + | +LL | let inner_shr = &*inner_uniq; + | ^^^^^^^^^^^^ +help: was later invalidated at offsets [0x0..0x4] by a write access + --> $DIR/interior_mut2.rs:LL:CC + | +LL | *c.get() = UnsafeCell::new(0); // now inner_shr gets invalidated + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: BACKTRACE: + = note: inside `main` at $DIR/interior_mut2.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/invalidate_against_protector1.rs b/src/tools/miri/tests/fail/stacked_borrows/invalidate_against_protector1.rs new file mode 100644 index 0000000000000..d0f43510c28ff --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/invalidate_against_protector1.rs @@ -0,0 +1,13 @@ +fn inner(x: *mut i32, _y: &mut i32) { + // If `x` and `y` alias, retagging is fine with this... but we really + // shouldn't be allowed to use `x` at all because `y` was assumed to be + // unique for the duration of this call. + let _val = unsafe { *x }; //~ ERROR: protect +} + +fn main() { + let mut x = 0; + let xraw = &mut x as *mut _; + let xref = unsafe { &mut *xraw }; + inner(xraw, xref); +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/invalidate_against_protector1.stderr b/src/tools/miri/tests/fail/stacked_borrows/invalidate_against_protector1.stderr new file mode 100644 index 0000000000000..f87bd2319abd7 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/invalidate_against_protector1.stderr @@ -0,0 +1,30 @@ +error: Undefined Behavior: not granting access to tag because that would remove [Unique for ] which is protected because it is an argument of call ID + --> $DIR/invalidate_against_protector1.rs:LL:CC + | +LL | let _val = unsafe { *x }; + | ^^ not granting access to tag because that would remove [Unique for ] which is protected because it is an argument of call ID + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a SharedReadWrite retag at offsets [0x0..0x4] + --> $DIR/invalidate_against_protector1.rs:LL:CC + | +LL | let xraw = &mut x as *mut _; + | ^^^^^^ +help: is this argument + --> $DIR/invalidate_against_protector1.rs:LL:CC + | +LL | fn inner(x: *mut i32, _y: &mut i32) { + | ^^ + = note: BACKTRACE: + = note: inside `inner` at $DIR/invalidate_against_protector1.rs:LL:CC +note: inside `main` at $DIR/invalidate_against_protector1.rs:LL:CC + --> $DIR/invalidate_against_protector1.rs:LL:CC + | +LL | inner(xraw, xref); + | ^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/invalidate_against_protector2.rs b/src/tools/miri/tests/fail/stacked_borrows/invalidate_against_protector2.rs new file mode 100644 index 0000000000000..f4e767302fd00 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/invalidate_against_protector2.rs @@ -0,0 +1,13 @@ +fn inner(x: *mut i32, _y: &i32) { + // If `x` and `y` alias, retagging is fine with this... but we really + // shouldn't be allowed to write to `x` at all because `y` was assumed to be + // immutable for the duration of this call. + unsafe { *x = 0 }; //~ ERROR: protect +} + +fn main() { + let mut x = 0; + let xraw = &mut x as *mut _; + let xref = unsafe { &*xraw }; + inner(xraw, xref); +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/invalidate_against_protector2.stderr b/src/tools/miri/tests/fail/stacked_borrows/invalidate_against_protector2.stderr new file mode 100644 index 0000000000000..07c51a39b825b --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/invalidate_against_protector2.stderr @@ -0,0 +1,30 @@ +error: Undefined Behavior: not granting access to tag because that would remove [SharedReadOnly for ] which is protected because it is an argument of call ID + --> $DIR/invalidate_against_protector2.rs:LL:CC + | +LL | unsafe { *x = 0 }; + | ^^^^^^ not granting access to tag because that would remove [SharedReadOnly for ] which is protected because it is an argument of call ID + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a SharedReadWrite retag at offsets [0x0..0x4] + --> $DIR/invalidate_against_protector2.rs:LL:CC + | +LL | let xraw = &mut x as *mut _; + | ^^^^^^ +help: is this argument + --> $DIR/invalidate_against_protector2.rs:LL:CC + | +LL | fn inner(x: *mut i32, _y: &i32) { + | ^^ + = note: BACKTRACE: + = note: inside `inner` at $DIR/invalidate_against_protector2.rs:LL:CC +note: inside `main` at $DIR/invalidate_against_protector2.rs:LL:CC + --> $DIR/invalidate_against_protector2.rs:LL:CC + | +LL | inner(xraw, xref); + | ^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/invalidate_against_protector3.rs b/src/tools/miri/tests/fail/stacked_borrows/invalidate_against_protector3.rs new file mode 100644 index 0000000000000..634eb97217c6c --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/invalidate_against_protector3.rs @@ -0,0 +1,15 @@ +use std::alloc::{alloc, Layout}; + +fn inner(x: *mut i32, _y: &i32) { + // If `x` and `y` alias, retagging is fine with this... but we really + // shouldn't be allowed to write to `x` at all because `y` was assumed to be + // immutable for the duration of this call. + unsafe { *x = 0 }; //~ ERROR: protect +} + +fn main() { + unsafe { + let ptr = alloc(Layout::for_value(&0i32)) as *mut i32; + inner(ptr, &*ptr); + }; +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/invalidate_against_protector3.stderr b/src/tools/miri/tests/fail/stacked_borrows/invalidate_against_protector3.stderr new file mode 100644 index 0000000000000..afda15ea160e2 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/invalidate_against_protector3.stderr @@ -0,0 +1,30 @@ +error: Undefined Behavior: not granting access to tag because that would remove [SharedReadOnly for ] which is protected because it is an argument of call ID + --> $DIR/invalidate_against_protector3.rs:LL:CC + | +LL | unsafe { *x = 0 }; + | ^^^^^^ not granting access to tag because that would remove [SharedReadOnly for ] which is protected because it is an argument of call ID + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created here, as the base tag for ALLOC + --> $DIR/invalidate_against_protector3.rs:LL:CC + | +LL | let ptr = alloc(Layout::for_value(&0i32)) as *mut i32; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: is this argument + --> $DIR/invalidate_against_protector3.rs:LL:CC + | +LL | fn inner(x: *mut i32, _y: &i32) { + | ^^ + = note: BACKTRACE: + = note: inside `inner` at $DIR/invalidate_against_protector3.rs:LL:CC +note: inside `main` at $DIR/invalidate_against_protector3.rs:LL:CC + --> $DIR/invalidate_against_protector3.rs:LL:CC + | +LL | inner(ptr, &*ptr); + | ^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/issue-miri-1050-1.rs b/src/tools/miri/tests/fail/stacked_borrows/issue-miri-1050-1.rs new file mode 100644 index 0000000000000..1e44cc6c800ee --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/issue-miri-1050-1.rs @@ -0,0 +1,8 @@ +//@error-pattern: pointer to 4 bytes starting at offset 0 is out-of-bounds + +fn main() { + unsafe { + let ptr = Box::into_raw(Box::new(0u16)); + drop(Box::from_raw(ptr as *mut u32)); + } +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/issue-miri-1050-1.stderr b/src/tools/miri/tests/fail/stacked_borrows/issue-miri-1050-1.stderr new file mode 100644 index 0000000000000..16c8810a8e6d9 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/issue-miri-1050-1.stderr @@ -0,0 +1,21 @@ +error: Undefined Behavior: out-of-bounds pointer use: ALLOC has size 2, so pointer to 4 bytes starting at offset 0 is out-of-bounds + --> RUSTLIB/alloc/src/boxed.rs:LL:CC + | +LL | Box(unsafe { Unique::new_unchecked(raw) }, alloc) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ out-of-bounds pointer use: ALLOC has size 2, so pointer to 4 bytes starting at offset 0 is out-of-bounds + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `std::boxed::Box::::from_raw_in` at RUSTLIB/alloc/src/boxed.rs:LL:CC + = note: inside `std::boxed::Box::::from_raw` at RUSTLIB/alloc/src/boxed.rs:LL:CC +note: inside `main` at $DIR/issue-miri-1050-1.rs:LL:CC + --> $DIR/issue-miri-1050-1.rs:LL:CC + | +LL | drop(Box::from_raw(ptr as *mut u32)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/issue-miri-1050-2.rs b/src/tools/miri/tests/fail/stacked_borrows/issue-miri-1050-2.rs new file mode 100644 index 0000000000000..6e90559a9ef5b --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/issue-miri-1050-2.rs @@ -0,0 +1,9 @@ +//@error-pattern: is a dangling pointer +use std::ptr::NonNull; + +fn main() { + unsafe { + let ptr = NonNull::::dangling(); + drop(Box::from_raw(ptr.as_ptr())); + } +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/issue-miri-1050-2.stderr b/src/tools/miri/tests/fail/stacked_borrows/issue-miri-1050-2.stderr new file mode 100644 index 0000000000000..d57e7662e504a --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/issue-miri-1050-2.stderr @@ -0,0 +1,21 @@ +error: Undefined Behavior: out-of-bounds pointer use: 0x4[noalloc] is a dangling pointer (it has no provenance) + --> RUSTLIB/alloc/src/boxed.rs:LL:CC + | +LL | Box(unsafe { Unique::new_unchecked(raw) }, alloc) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ out-of-bounds pointer use: 0x4[noalloc] is a dangling pointer (it has no provenance) + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `std::boxed::Box::::from_raw_in` at RUSTLIB/alloc/src/boxed.rs:LL:CC + = note: inside `std::boxed::Box::::from_raw` at RUSTLIB/alloc/src/boxed.rs:LL:CC +note: inside `main` at $DIR/issue-miri-1050-2.rs:LL:CC + --> $DIR/issue-miri-1050-2.rs:LL:CC + | +LL | drop(Box::from_raw(ptr.as_ptr())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/load_invalid_mut.rs b/src/tools/miri/tests/fail/stacked_borrows/load_invalid_mut.rs new file mode 100644 index 0000000000000..5c99c82da6d6a --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/load_invalid_mut.rs @@ -0,0 +1,12 @@ +// Make sure we catch this even without validation +//@compile-flags: -Zmiri-disable-validation + +// Make sure that we cannot load from memory a `&mut` that got already invalidated. +fn main() { + let x = &mut 42; + let xraw = x as *mut _; + let xref = unsafe { &mut *xraw }; + let xref_in_mem = Box::new(xref); + let _val = unsafe { *xraw }; // invalidate xref + let _val = *xref_in_mem; //~ ERROR: /retag .* tag does not exist in the borrow stack/ +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/load_invalid_mut.stderr b/src/tools/miri/tests/fail/stacked_borrows/load_invalid_mut.stderr new file mode 100644 index 0000000000000..08dc171c9eef0 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/load_invalid_mut.stderr @@ -0,0 +1,28 @@ +error: Undefined Behavior: trying to retag from for Unique permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + --> $DIR/load_invalid_mut.rs:LL:CC + | +LL | let _val = *xref_in_mem; + | ^^^^^^^^^^^^ + | | + | trying to retag from for Unique permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + | this error occurs as part of retag at ALLOC[0x0..0x4] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a Unique retag at offsets [0x0..0x4] + --> $DIR/load_invalid_mut.rs:LL:CC + | +LL | let xref_in_mem = Box::new(xref); + | ^^^^^^^^^^^^^^ +help: was later invalidated at offsets [0x0..0x4] by a read access + --> $DIR/load_invalid_mut.rs:LL:CC + | +LL | let _val = unsafe { *xraw }; // invalidate xref + | ^^^^^ + = note: BACKTRACE: + = note: inside `main` at $DIR/load_invalid_mut.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/load_invalid_shr.rs b/src/tools/miri/tests/fail/stacked_borrows/load_invalid_shr.rs new file mode 100644 index 0000000000000..8f94cc07a24f7 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/load_invalid_shr.rs @@ -0,0 +1,12 @@ +// Make sure we catch this even without validation +//@compile-flags: -Zmiri-disable-validation + +// Make sure that we cannot load from memory a `&` that got already invalidated. +fn main() { + let x = &mut 42; + let xraw = x as *mut _; + let xref = unsafe { &*xraw }; + let xref_in_mem = Box::new(xref); + unsafe { *xraw = 42 }; // unfreeze + let _val = *xref_in_mem; //~ ERROR: /retag .* tag does not exist in the borrow stack/ +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/load_invalid_shr.stderr b/src/tools/miri/tests/fail/stacked_borrows/load_invalid_shr.stderr new file mode 100644 index 0000000000000..50bbed2b295c9 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/load_invalid_shr.stderr @@ -0,0 +1,28 @@ +error: Undefined Behavior: trying to retag from for SharedReadOnly permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + --> $DIR/load_invalid_shr.rs:LL:CC + | +LL | let _val = *xref_in_mem; + | ^^^^^^^^^^^^ + | | + | trying to retag from for SharedReadOnly permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + | this error occurs as part of retag at ALLOC[0x0..0x4] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a SharedReadOnly retag at offsets [0x0..0x4] + --> $DIR/load_invalid_shr.rs:LL:CC + | +LL | let xref_in_mem = Box::new(xref); + | ^^^^^^^^^^^^^^ +help: was later invalidated at offsets [0x0..0x4] by a write access + --> $DIR/load_invalid_shr.rs:LL:CC + | +LL | unsafe { *xraw = 42 }; // unfreeze + | ^^^^^^^^^^ + = note: BACKTRACE: + = note: inside `main` at $DIR/load_invalid_shr.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/mut_exclusive_violation1.rs b/src/tools/miri/tests/fail/stacked_borrows/mut_exclusive_violation1.rs new file mode 100644 index 0000000000000..f6fcdf1acdd2a --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/mut_exclusive_violation1.rs @@ -0,0 +1,33 @@ +fn demo_mut_advanced_unique(our: &mut i32) -> i32 { + unknown_code_1(&*our); + + // This "re-asserts" uniqueness of the reference: After writing, we know + // our tag is at the top of the stack. + *our = 5; + + unknown_code_2(); + + // We know this will return 5 + *our +} + +// Now comes the evil context +use std::ptr; + +static mut LEAK: *mut i32 = ptr::null_mut(); + +fn unknown_code_1(x: &i32) { + unsafe { + LEAK = x as *const _ as *mut _; + } +} + +fn unknown_code_2() { + unsafe { + *LEAK = 7; //~ ERROR: /write access .* tag does not exist in the borrow stack/ + } +} + +fn main() { + demo_mut_advanced_unique(&mut 0); +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/mut_exclusive_violation1.stderr b/src/tools/miri/tests/fail/stacked_borrows/mut_exclusive_violation1.stderr new file mode 100644 index 0000000000000..1c7f8e12d3d78 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/mut_exclusive_violation1.stderr @@ -0,0 +1,38 @@ +error: Undefined Behavior: attempting a write access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + --> $DIR/mut_exclusive_violation1.rs:LL:CC + | +LL | *LEAK = 7; + | ^^^^^^^^^ + | | + | attempting a write access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + | this error occurs as part of an access at ALLOC[0x0..0x4] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a SharedReadOnly retag at offsets [0x0..0x4] + --> $DIR/mut_exclusive_violation1.rs:LL:CC + | +LL | LEAK = x as *const _ as *mut _; + | ^ +help: was later invalidated at offsets [0x0..0x4] by a write access + --> $DIR/mut_exclusive_violation1.rs:LL:CC + | +LL | *our = 5; + | ^^^^^^^^ + = note: BACKTRACE: + = note: inside `unknown_code_2` at $DIR/mut_exclusive_violation1.rs:LL:CC +note: inside `demo_mut_advanced_unique` at $DIR/mut_exclusive_violation1.rs:LL:CC + --> $DIR/mut_exclusive_violation1.rs:LL:CC + | +LL | unknown_code_2(); + | ^^^^^^^^^^^^^^^^ +note: inside `main` at $DIR/mut_exclusive_violation1.rs:LL:CC + --> $DIR/mut_exclusive_violation1.rs:LL:CC + | +LL | demo_mut_advanced_unique(&mut 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/mut_exclusive_violation2.rs b/src/tools/miri/tests/fail/stacked_borrows/mut_exclusive_violation2.rs new file mode 100644 index 0000000000000..2305ce746512a --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/mut_exclusive_violation2.rs @@ -0,0 +1,12 @@ +use std::ptr::NonNull; + +fn main() { + unsafe { + let x = &mut 0; + let mut ptr1 = NonNull::from(x); + let mut ptr2 = ptr1.clone(); + let raw1 = ptr1.as_mut(); + let _raw2 = ptr2.as_mut(); + let _val = *raw1; //~ ERROR: /read access .* tag does not exist in the borrow stack/ + } +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/mut_exclusive_violation2.stderr b/src/tools/miri/tests/fail/stacked_borrows/mut_exclusive_violation2.stderr new file mode 100644 index 0000000000000..43b5325fc541a --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/mut_exclusive_violation2.stderr @@ -0,0 +1,28 @@ +error: Undefined Behavior: attempting a read access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + --> $DIR/mut_exclusive_violation2.rs:LL:CC + | +LL | let _val = *raw1; + | ^^^^^ + | | + | attempting a read access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + | this error occurs as part of an access at ALLOC[0x0..0x4] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a Unique retag at offsets [0x0..0x4] + --> $DIR/mut_exclusive_violation2.rs:LL:CC + | +LL | let raw1 = ptr1.as_mut(); + | ^^^^^^^^^^^^^ +help: was later invalidated at offsets [0x0..0x4] by a Unique retag + --> $DIR/mut_exclusive_violation2.rs:LL:CC + | +LL | let _raw2 = ptr2.as_mut(); + | ^^^^^^^^^^^^^ + = note: BACKTRACE: + = note: inside `main` at $DIR/mut_exclusive_violation2.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/newtype_retagging.rs b/src/tools/miri/tests/fail/stacked_borrows/newtype_retagging.rs new file mode 100644 index 0000000000000..6e7413cff5d4b --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/newtype_retagging.rs @@ -0,0 +1,19 @@ +//@compile-flags: -Zmiri-retag-fields +//@error-pattern: which is protected +struct Newtype<'a>(&'a mut i32); + +fn dealloc_while_running(_n: Newtype<'_>, dealloc: impl FnOnce()) { + dealloc(); +} + +// Make sure that we protect references inside structs. +fn main() { + let ptr = Box::into_raw(Box::new(0i32)); + #[rustfmt::skip] // I like my newlines + unsafe { + dealloc_while_running( + Newtype(&mut *ptr), + || drop(Box::from_raw(ptr)), + ) + }; +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/newtype_retagging.stderr b/src/tools/miri/tests/fail/stacked_borrows/newtype_retagging.stderr new file mode 100644 index 0000000000000..06a9b86c6f45a --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/newtype_retagging.stderr @@ -0,0 +1,44 @@ +error: Undefined Behavior: not granting access to tag because that would remove [Unique for ] which is protected because it is an argument of call ID + --> RUSTLIB/alloc/src/boxed.rs:LL:CC + | +LL | Box(unsafe { Unique::new_unchecked(raw) }, alloc) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not granting access to tag because that would remove [Unique for ] which is protected because it is an argument of call ID + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a SharedReadWrite retag at offsets [0x0..0x4] + --> $DIR/newtype_retagging.rs:LL:CC + | +LL | let ptr = Box::into_raw(Box::new(0i32)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: is this argument + --> $DIR/newtype_retagging.rs:LL:CC + | +LL | fn dealloc_while_running(_n: Newtype<'_>, dealloc: impl FnOnce()) { + | ^^ + = note: BACKTRACE: + = note: inside `std::boxed::Box::::from_raw_in` at RUSTLIB/alloc/src/boxed.rs:LL:CC + = note: inside `std::boxed::Box::::from_raw` at RUSTLIB/alloc/src/boxed.rs:LL:CC +note: inside closure at $DIR/newtype_retagging.rs:LL:CC + --> $DIR/newtype_retagging.rs:LL:CC + | +LL | || drop(Box::from_raw(ptr)), + | ^^^^^^^^^^^^^^^^^^ +note: inside `dealloc_while_running::<[closure@$DIR/newtype_retagging.rs:LL:CC]>` at $DIR/newtype_retagging.rs:LL:CC + --> $DIR/newtype_retagging.rs:LL:CC + | +LL | dealloc(); + | ^^^^^^^^^ +note: inside `main` at $DIR/newtype_retagging.rs:LL:CC + --> $DIR/newtype_retagging.rs:LL:CC + | +LL | / dealloc_while_running( +LL | | Newtype(&mut *ptr), +LL | | || drop(Box::from_raw(ptr)), +LL | | ) + | |_________^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/outdated_local.rs b/src/tools/miri/tests/fail/stacked_borrows/outdated_local.rs new file mode 100644 index 0000000000000..4262157d1e3df --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/outdated_local.rs @@ -0,0 +1,9 @@ +fn main() { + let mut x = 0; + let y: *const i32 = &x; + x = 1; // this invalidates y by reactivating the lowermost uniq borrow for this local + + assert_eq!(unsafe { *y }, 1); //~ ERROR: /read access .* tag does not exist in the borrow stack/ + + assert_eq!(x, 1); +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/outdated_local.stderr b/src/tools/miri/tests/fail/stacked_borrows/outdated_local.stderr new file mode 100644 index 0000000000000..8c2bba5391888 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/outdated_local.stderr @@ -0,0 +1,28 @@ +error: Undefined Behavior: attempting a read access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + --> $DIR/outdated_local.rs:LL:CC + | +LL | assert_eq!(unsafe { *y }, 1); + | ^^ + | | + | attempting a read access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + | this error occurs as part of an access at ALLOC[0x0..0x4] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a SharedReadOnly retag at offsets [0x0..0x4] + --> $DIR/outdated_local.rs:LL:CC + | +LL | let y: *const i32 = &x; + | ^^ +help: was later invalidated at offsets [0x0..0x4] by a write access + --> $DIR/outdated_local.rs:LL:CC + | +LL | x = 1; // this invalidates y by reactivating the lowermost uniq borrow for this local + | ^^^^^ + = note: BACKTRACE: + = note: inside `main` at $DIR/outdated_local.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/pass_invalid_mut.rs b/src/tools/miri/tests/fail/stacked_borrows/pass_invalid_mut.rs new file mode 100644 index 0000000000000..8db2c149b17e0 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/pass_invalid_mut.rs @@ -0,0 +1,10 @@ +// Make sure that we cannot pass by argument a `&mut` that got already invalidated. +fn foo(_: &mut i32) {} + +fn main() { + let x = &mut 42; + let xraw = x as *mut _; + let xref = unsafe { &mut *xraw }; + let _val = unsafe { *xraw }; // invalidate xref + foo(xref); //~ ERROR: /retag .* tag does not exist in the borrow stack/ +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/pass_invalid_mut.stderr b/src/tools/miri/tests/fail/stacked_borrows/pass_invalid_mut.stderr new file mode 100644 index 0000000000000..d7ab930aa3785 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/pass_invalid_mut.stderr @@ -0,0 +1,28 @@ +error: Undefined Behavior: trying to retag from for SharedReadWrite permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + --> $DIR/pass_invalid_mut.rs:LL:CC + | +LL | foo(xref); + | ^^^^ + | | + | trying to retag from for SharedReadWrite permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + | this error occurs as part of two-phase retag at ALLOC[0x0..0x4] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a Unique retag at offsets [0x0..0x4] + --> $DIR/pass_invalid_mut.rs:LL:CC + | +LL | let xref = unsafe { &mut *xraw }; + | ^^^^^^^^^^ +help: was later invalidated at offsets [0x0..0x4] by a read access + --> $DIR/pass_invalid_mut.rs:LL:CC + | +LL | let _val = unsafe { *xraw }; // invalidate xref + | ^^^^^ + = note: BACKTRACE: + = note: inside `main` at $DIR/pass_invalid_mut.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/pass_invalid_shr.rs b/src/tools/miri/tests/fail/stacked_borrows/pass_invalid_shr.rs new file mode 100644 index 0000000000000..903c2733107bb --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/pass_invalid_shr.rs @@ -0,0 +1,10 @@ +// Make sure that we cannot pass by argument a `&` that got already invalidated. +fn foo(_: &i32) {} + +fn main() { + let x = &mut 42; + let xraw = x as *mut _; + let xref = unsafe { &*xraw }; + unsafe { *xraw = 42 }; // unfreeze + foo(xref); //~ ERROR: /retag .* tag does not exist in the borrow stack/ +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/pass_invalid_shr.stderr b/src/tools/miri/tests/fail/stacked_borrows/pass_invalid_shr.stderr new file mode 100644 index 0000000000000..c14b35c75c83d --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/pass_invalid_shr.stderr @@ -0,0 +1,28 @@ +error: Undefined Behavior: trying to retag from for SharedReadOnly permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + --> $DIR/pass_invalid_shr.rs:LL:CC + | +LL | foo(xref); + | ^^^^ + | | + | trying to retag from for SharedReadOnly permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + | this error occurs as part of retag at ALLOC[0x0..0x4] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a SharedReadOnly retag at offsets [0x0..0x4] + --> $DIR/pass_invalid_shr.rs:LL:CC + | +LL | let xref = unsafe { &*xraw }; + | ^^^^^^ +help: was later invalidated at offsets [0x0..0x4] by a write access + --> $DIR/pass_invalid_shr.rs:LL:CC + | +LL | unsafe { *xraw = 42 }; // unfreeze + | ^^^^^^^^^^ + = note: BACKTRACE: + = note: inside `main` at $DIR/pass_invalid_shr.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/pointer_smuggling.rs b/src/tools/miri/tests/fail/stacked_borrows/pointer_smuggling.rs new file mode 100644 index 0000000000000..e1c3ff4928983 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/pointer_smuggling.rs @@ -0,0 +1,20 @@ +static mut PTR: *mut u8 = 0 as *mut _; + +fn fun1(x: &mut u8) { + unsafe { + PTR = x; + } +} + +fn fun2() { + // Now we use a pointer we are not allowed to use + let _x = unsafe { *PTR }; //~ ERROR: /read access .* tag does not exist in the borrow stack/ +} + +fn main() { + let mut val = 0; + let val = &mut val; + fun1(val); + *val = 2; // this invalidates any raw ptrs `fun1` might have created. + fun2(); // if they now use a raw ptr they break our reference +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/pointer_smuggling.stderr b/src/tools/miri/tests/fail/stacked_borrows/pointer_smuggling.stderr new file mode 100644 index 0000000000000..6415af1e18bbf --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/pointer_smuggling.stderr @@ -0,0 +1,33 @@ +error: Undefined Behavior: attempting a read access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + --> $DIR/pointer_smuggling.rs:LL:CC + | +LL | let _x = unsafe { *PTR }; + | ^^^^ + | | + | attempting a read access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + | this error occurs as part of an access at ALLOC[0x0..0x1] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a SharedReadWrite retag at offsets [0x0..0x1] + --> $DIR/pointer_smuggling.rs:LL:CC + | +LL | PTR = x; + | ^ +help: was later invalidated at offsets [0x0..0x1] by a write access + --> $DIR/pointer_smuggling.rs:LL:CC + | +LL | *val = 2; // this invalidates any raw ptrs `fun1` might have created. + | ^^^^^^^^ + = note: BACKTRACE: + = note: inside `fun2` at $DIR/pointer_smuggling.rs:LL:CC +note: inside `main` at $DIR/pointer_smuggling.rs:LL:CC + --> $DIR/pointer_smuggling.rs:LL:CC + | +LL | fun2(); // if they now use a raw ptr they break our reference + | ^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/raw_tracking.rs b/src/tools/miri/tests/fail/stacked_borrows/raw_tracking.rs new file mode 100644 index 0000000000000..15306e0825b22 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/raw_tracking.rs @@ -0,0 +1,11 @@ +//! This demonstrates a provenance problem that requires tracking of raw pointers to be detected. + +fn main() { + let mut l = 13; + let raw1 = &mut l as *mut _; + let raw2 = &mut l as *mut _; // invalidates raw1 + // Without raw pointer tracking, Stacked Borrows cannot distinguish raw1 and raw2, and thus + // fails to realize that raw1 should not be used any more. + unsafe { *raw1 = 13 }; //~ ERROR: /write access .* tag does not exist in the borrow stack/ + unsafe { *raw2 = 13 }; +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/raw_tracking.stderr b/src/tools/miri/tests/fail/stacked_borrows/raw_tracking.stderr new file mode 100644 index 0000000000000..d75934445e6d2 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/raw_tracking.stderr @@ -0,0 +1,28 @@ +error: Undefined Behavior: attempting a write access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + --> $DIR/raw_tracking.rs:LL:CC + | +LL | unsafe { *raw1 = 13 }; + | ^^^^^^^^^^ + | | + | attempting a write access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + | this error occurs as part of an access at ALLOC[0x0..0x4] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a SharedReadWrite retag at offsets [0x0..0x4] + --> $DIR/raw_tracking.rs:LL:CC + | +LL | let raw1 = &mut l as *mut _; + | ^^^^^^ +help: was later invalidated at offsets [0x0..0x4] by a Unique retag + --> $DIR/raw_tracking.rs:LL:CC + | +LL | let raw2 = &mut l as *mut _; // invalidates raw1 + | ^^^^^^ + = note: BACKTRACE: + = note: inside `main` at $DIR/raw_tracking.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/return_invalid_mut.rs b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_mut.rs new file mode 100644 index 0000000000000..7d9a6f11aff97 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_mut.rs @@ -0,0 +1,11 @@ +// Make sure that we cannot return a `&mut` that got already invalidated. +fn foo(x: &mut (i32, i32)) -> &mut i32 { + let xraw = x as *mut (i32, i32); + let ret = unsafe { &mut (*xraw).1 }; + let _val = unsafe { *xraw }; // invalidate xref + ret //~ ERROR: /retag .* tag does not exist in the borrow stack/ +} + +fn main() { + foo(&mut (1, 2)); +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/return_invalid_mut.stderr b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_mut.stderr new file mode 100644 index 0000000000000..9deb0c41742f3 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_mut.stderr @@ -0,0 +1,33 @@ +error: Undefined Behavior: trying to retag from for Unique permission at ALLOC[0x4], but that tag does not exist in the borrow stack for this location + --> $DIR/return_invalid_mut.rs:LL:CC + | +LL | ret + | ^^^ + | | + | trying to retag from for Unique permission at ALLOC[0x4], but that tag does not exist in the borrow stack for this location + | this error occurs as part of retag at ALLOC[0x4..0x8] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a Unique retag at offsets [0x4..0x8] + --> $DIR/return_invalid_mut.rs:LL:CC + | +LL | let ret = unsafe { &mut (*xraw).1 }; + | ^^^^^^^^^^^^^^ +help: was later invalidated at offsets [0x0..0x8] by a read access + --> $DIR/return_invalid_mut.rs:LL:CC + | +LL | let _val = unsafe { *xraw }; // invalidate xref + | ^^^^^ + = note: BACKTRACE: + = note: inside `foo` at $DIR/return_invalid_mut.rs:LL:CC +note: inside `main` at $DIR/return_invalid_mut.rs:LL:CC + --> $DIR/return_invalid_mut.rs:LL:CC + | +LL | foo(&mut (1, 2)); + | ^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/return_invalid_mut_option.rs b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_mut_option.rs new file mode 100644 index 0000000000000..7fa9cf77d44b2 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_mut_option.rs @@ -0,0 +1,16 @@ +// Make sure that we cannot return a `&mut` that got already invalidated, not even in an `Option`. +// Due to shallow reborrowing, the error only surfaces when we look into the `Option`. +fn foo(x: &mut (i32, i32)) -> Option<&mut i32> { + let xraw = x as *mut (i32, i32); + let ret = unsafe { &mut (*xraw).1 }; // let-bind to avoid 2phase + let ret = Some(ret); + let _val = unsafe { *xraw }; // invalidate xref + ret +} + +fn main() { + match foo(&mut (1, 2)) { + Some(_x) => {} //~ ERROR: /retag .* tag does not exist in the borrow stack/ + None => {} + } +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/return_invalid_mut_option.stderr b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_mut_option.stderr new file mode 100644 index 0000000000000..1068c286c62fa --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_mut_option.stderr @@ -0,0 +1,28 @@ +error: Undefined Behavior: trying to retag from for Unique permission at ALLOC[0x4], but that tag does not exist in the borrow stack for this location + --> $DIR/return_invalid_mut_option.rs:LL:CC + | +LL | Some(_x) => {} + | ^^ + | | + | trying to retag from for Unique permission at ALLOC[0x4], but that tag does not exist in the borrow stack for this location + | this error occurs as part of retag at ALLOC[0x4..0x8] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a Unique retag at offsets [0x4..0x8] + --> $DIR/return_invalid_mut_option.rs:LL:CC + | +LL | let ret = Some(ret); + | ^^^ +help: was later invalidated at offsets [0x0..0x8] by a read access + --> $DIR/return_invalid_mut_option.rs:LL:CC + | +LL | let _val = unsafe { *xraw }; // invalidate xref + | ^^^^^ + = note: BACKTRACE: + = note: inside `main` at $DIR/return_invalid_mut_option.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/return_invalid_mut_tuple.rs b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_mut_tuple.rs new file mode 100644 index 0000000000000..c94fef90542fd --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_mut_tuple.rs @@ -0,0 +1,12 @@ +// Make sure that we cannot return a `&mut` that got already invalidated, not even in a tuple. +// Due to shallow reborrowing, the error only surfaces when we look into the tuple. +fn foo(x: &mut (i32, i32)) -> (&mut i32,) { + let xraw = x as *mut (i32, i32); + let ret = (unsafe { &mut (*xraw).1 },); + let _val = unsafe { *xraw }; // invalidate xref + ret +} + +fn main() { + foo(&mut (1, 2)).0; //~ ERROR: /retag .* tag does not exist in the borrow stack/ +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/return_invalid_mut_tuple.stderr b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_mut_tuple.stderr new file mode 100644 index 0000000000000..79de9b668cf2b --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_mut_tuple.stderr @@ -0,0 +1,28 @@ +error: Undefined Behavior: trying to retag from for Unique permission at ALLOC[0x4], but that tag does not exist in the borrow stack for this location + --> $DIR/return_invalid_mut_tuple.rs:LL:CC + | +LL | foo(&mut (1, 2)).0; + | ^^^^^^^^^^^^^^^^^^ + | | + | trying to retag from for Unique permission at ALLOC[0x4], but that tag does not exist in the borrow stack for this location + | this error occurs as part of retag at ALLOC[0x4..0x8] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a Unique retag at offsets [0x4..0x8] + --> $DIR/return_invalid_mut_tuple.rs:LL:CC + | +LL | let ret = (unsafe { &mut (*xraw).1 },); + | ^^^^^^^^^^^^^^ +help: was later invalidated at offsets [0x0..0x8] by a read access + --> $DIR/return_invalid_mut_tuple.rs:LL:CC + | +LL | let _val = unsafe { *xraw }; // invalidate xref + | ^^^^^ + = note: BACKTRACE: + = note: inside `main` at $DIR/return_invalid_mut_tuple.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr.rs b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr.rs new file mode 100644 index 0000000000000..45526498dadf5 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr.rs @@ -0,0 +1,11 @@ +// Make sure that we cannot return a `&` that got already invalidated. +fn foo(x: &mut (i32, i32)) -> &i32 { + let xraw = x as *mut (i32, i32); + let ret = unsafe { &(*xraw).1 }; + unsafe { *xraw = (42, 23) }; // unfreeze + ret //~ ERROR: /retag .* tag does not exist in the borrow stack/ +} + +fn main() { + foo(&mut (1, 2)); +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr.stderr b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr.stderr new file mode 100644 index 0000000000000..dd651517c2fb0 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr.stderr @@ -0,0 +1,33 @@ +error: Undefined Behavior: trying to retag from for SharedReadOnly permission at ALLOC[0x4], but that tag does not exist in the borrow stack for this location + --> $DIR/return_invalid_shr.rs:LL:CC + | +LL | ret + | ^^^ + | | + | trying to retag from for SharedReadOnly permission at ALLOC[0x4], but that tag does not exist in the borrow stack for this location + | this error occurs as part of retag at ALLOC[0x4..0x8] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a SharedReadOnly retag at offsets [0x4..0x8] + --> $DIR/return_invalid_shr.rs:LL:CC + | +LL | let ret = unsafe { &(*xraw).1 }; + | ^^^^^^^^^^ +help: was later invalidated at offsets [0x0..0x8] by a write access + --> $DIR/return_invalid_shr.rs:LL:CC + | +LL | unsafe { *xraw = (42, 23) }; // unfreeze + | ^^^^^^^^^^^^^^^^ + = note: BACKTRACE: + = note: inside `foo` at $DIR/return_invalid_shr.rs:LL:CC +note: inside `main` at $DIR/return_invalid_shr.rs:LL:CC + --> $DIR/return_invalid_shr.rs:LL:CC + | +LL | foo(&mut (1, 2)); + | ^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr_option.rs b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr_option.rs new file mode 100644 index 0000000000000..3a028ceed86ae --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr_option.rs @@ -0,0 +1,15 @@ +// Make sure that we cannot return a `&` that got already invalidated, not even in an `Option`. +// Due to shallow reborrowing, the error only surfaces when we look into the `Option`. +fn foo(x: &mut (i32, i32)) -> Option<&i32> { + let xraw = x as *mut (i32, i32); + let ret = Some(unsafe { &(*xraw).1 }); + unsafe { *xraw = (42, 23) }; // unfreeze + ret +} + +fn main() { + match foo(&mut (1, 2)) { + Some(_x) => {} //~ ERROR: /retag .* tag does not exist in the borrow stack/ + None => {} + } +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr_option.stderr b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr_option.stderr new file mode 100644 index 0000000000000..f45456305db29 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr_option.stderr @@ -0,0 +1,28 @@ +error: Undefined Behavior: trying to retag from for SharedReadOnly permission at ALLOC[0x4], but that tag does not exist in the borrow stack for this location + --> $DIR/return_invalid_shr_option.rs:LL:CC + | +LL | Some(_x) => {} + | ^^ + | | + | trying to retag from for SharedReadOnly permission at ALLOC[0x4], but that tag does not exist in the borrow stack for this location + | this error occurs as part of retag at ALLOC[0x4..0x8] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a SharedReadOnly retag at offsets [0x4..0x8] + --> $DIR/return_invalid_shr_option.rs:LL:CC + | +LL | let ret = Some(unsafe { &(*xraw).1 }); + | ^^^^^^^^^^ +help: was later invalidated at offsets [0x0..0x8] by a write access + --> $DIR/return_invalid_shr_option.rs:LL:CC + | +LL | unsafe { *xraw = (42, 23) }; // unfreeze + | ^^^^^^^^^^^^^^^^ + = note: BACKTRACE: + = note: inside `main` at $DIR/return_invalid_shr_option.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr_tuple.rs b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr_tuple.rs new file mode 100644 index 0000000000000..e4536626dbf2c --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr_tuple.rs @@ -0,0 +1,12 @@ +// Make sure that we cannot return a `&` that got already invalidated, not even in a tuple. +// Due to shallow reborrowing, the error only surfaces when we look into the tuple. +fn foo(x: &mut (i32, i32)) -> (&i32,) { + let xraw = x as *mut (i32, i32); + let ret = (unsafe { &(*xraw).1 },); + unsafe { *xraw = (42, 23) }; // unfreeze + ret +} + +fn main() { + foo(&mut (1, 2)).0; //~ ERROR: /retag .* tag does not exist in the borrow stack/ +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr_tuple.stderr b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr_tuple.stderr new file mode 100644 index 0000000000000..2e41f505bb9d2 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr_tuple.stderr @@ -0,0 +1,28 @@ +error: Undefined Behavior: trying to retag from for SharedReadOnly permission at ALLOC[0x4], but that tag does not exist in the borrow stack for this location + --> $DIR/return_invalid_shr_tuple.rs:LL:CC + | +LL | foo(&mut (1, 2)).0; + | ^^^^^^^^^^^^^^^^^^ + | | + | trying to retag from for SharedReadOnly permission at ALLOC[0x4], but that tag does not exist in the borrow stack for this location + | this error occurs as part of retag at ALLOC[0x4..0x8] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a SharedReadOnly retag at offsets [0x4..0x8] + --> $DIR/return_invalid_shr_tuple.rs:LL:CC + | +LL | let ret = (unsafe { &(*xraw).1 },); + | ^^^^^^^^^^ +help: was later invalidated at offsets [0x0..0x8] by a write access + --> $DIR/return_invalid_shr_tuple.rs:LL:CC + | +LL | unsafe { *xraw = (42, 23) }; // unfreeze + | ^^^^^^^^^^^^^^^^ + = note: BACKTRACE: + = note: inside `main` at $DIR/return_invalid_shr_tuple.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/shared_rw_borrows_are_weak1.rs b/src/tools/miri/tests/fail/stacked_borrows/shared_rw_borrows_are_weak1.rs new file mode 100644 index 0000000000000..2450ec4b4a1d0 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/shared_rw_borrows_are_weak1.rs @@ -0,0 +1,16 @@ +// We want to test that granting a SharedReadWrite will be added +// *below* an already granted Unique -- so writing to +// the SharedReadWrite will invalidate the Unique. + +use std::cell::Cell; +use std::mem; + +fn main() { + unsafe { + let x = &mut Cell::new(0); + let y: &mut Cell = mem::transmute(&mut *x); // launder lifetime + let shr_rw = &*x; // thanks to interior mutability this will be a SharedReadWrite + shr_rw.set(1); + y.get_mut(); //~ ERROR: /retag .* tag does not exist in the borrow stack/ + } +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/shared_rw_borrows_are_weak1.stderr b/src/tools/miri/tests/fail/stacked_borrows/shared_rw_borrows_are_weak1.stderr new file mode 100644 index 0000000000000..3a139c3ab2120 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/shared_rw_borrows_are_weak1.stderr @@ -0,0 +1,28 @@ +error: Undefined Behavior: trying to retag from for SharedReadWrite permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + --> $DIR/shared_rw_borrows_are_weak1.rs:LL:CC + | +LL | y.get_mut(); + | ^^^^^^^^^^^ + | | + | trying to retag from for SharedReadWrite permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + | this error occurs as part of two-phase retag at ALLOC[0x0..0x4] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a Unique retag at offsets [0x0..0x4] + --> $DIR/shared_rw_borrows_are_weak1.rs:LL:CC + | +LL | let y: &mut Cell = mem::transmute(&mut *x); // launder lifetime + | ^^^^^^^^^^^^^^^^^^^^^^^ +help: was later invalidated at offsets [0x0..0x4] by a Unique retag + --> $DIR/shared_rw_borrows_are_weak1.rs:LL:CC + | +LL | shr_rw.set(1); + | ^^^^^^^^^^^^^ + = note: BACKTRACE: + = note: inside `main` at $DIR/shared_rw_borrows_are_weak1.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/shared_rw_borrows_are_weak2.rs b/src/tools/miri/tests/fail/stacked_borrows/shared_rw_borrows_are_weak2.rs new file mode 100644 index 0000000000000..012e9561ca848 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/shared_rw_borrows_are_weak2.rs @@ -0,0 +1,17 @@ +// We want to test that granting a SharedReadWrite will be added +// *below* an already granted SharedReadWrite -- so writing to +// the SharedReadWrite will invalidate the SharedReadWrite. +//@normalize-stderr-test: "0x[0-9a-fA-F]+" -> "$$HEX" + +use std::cell::RefCell; +use std::mem; + +fn main() { + unsafe { + let x = &mut RefCell::new(0); + let y: &i32 = mem::transmute(&*x.borrow()); // launder lifetime + let shr_rw = &*x; // thanks to interior mutability this will be a SharedReadWrite + shr_rw.replace(1); + let _val = *y; //~ ERROR: /read access .* tag does not exist in the borrow stack/ + } +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/shared_rw_borrows_are_weak2.stderr b/src/tools/miri/tests/fail/stacked_borrows/shared_rw_borrows_are_weak2.stderr new file mode 100644 index 0000000000000..0609a73e79315 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/shared_rw_borrows_are_weak2.stderr @@ -0,0 +1,28 @@ +error: Undefined Behavior: attempting a read access using at ALLOC[$HEX], but that tag does not exist in the borrow stack for this location + --> $DIR/shared_rw_borrows_are_weak2.rs:LL:CC + | +LL | let _val = *y; + | ^^ + | | + | attempting a read access using at ALLOC[$HEX], but that tag does not exist in the borrow stack for this location + | this error occurs as part of an access at ALLOC[$HEX..$HEX] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a SharedReadOnly retag at offsets [$HEX..$HEX] + --> $DIR/shared_rw_borrows_are_weak2.rs:LL:CC + | +LL | let y: &i32 = mem::transmute(&*x.borrow()); // launder lifetime + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: was later invalidated at offsets [$HEX..$HEX] by a Unique retag + --> $DIR/shared_rw_borrows_are_weak2.rs:LL:CC + | +LL | shr_rw.replace(1); + | ^^^^^^^^^^^^^^^^^ + = note: BACKTRACE: + = note: inside `main` at $DIR/shared_rw_borrows_are_weak2.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/shr_frozen_violation1.rs b/src/tools/miri/tests/fail/stacked_borrows/shr_frozen_violation1.rs new file mode 100644 index 0000000000000..d1322dc6e57bb --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/shr_frozen_violation1.rs @@ -0,0 +1,15 @@ +fn foo(x: &mut i32) -> i32 { + *x = 5; + unknown_code(&*x); + *x // must return 5 +} + +fn main() { + println!("{}", foo(&mut 0)); +} + +fn unknown_code(x: &i32) { + unsafe { + *(x as *const i32 as *mut i32) = 7; //~ ERROR: /write access .* only grants SharedReadOnly permission/ + } +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/shr_frozen_violation1.stderr b/src/tools/miri/tests/fail/stacked_borrows/shr_frozen_violation1.stderr new file mode 100644 index 0000000000000..0818d07da48e5 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/shr_frozen_violation1.stderr @@ -0,0 +1,33 @@ +error: Undefined Behavior: attempting a write access using at ALLOC[0x0], but that tag only grants SharedReadOnly permission for this location + --> $DIR/shr_frozen_violation1.rs:LL:CC + | +LL | *(x as *const i32 as *mut i32) = 7; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | attempting a write access using at ALLOC[0x0], but that tag only grants SharedReadOnly permission for this location + | this error occurs as part of an access at ALLOC[0x0..0x4] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a SharedReadOnly retag at offsets [0x0..0x4] + --> $DIR/shr_frozen_violation1.rs:LL:CC + | +LL | *(x as *const i32 as *mut i32) = 7; + | ^ + = note: BACKTRACE: + = note: inside `unknown_code` at $DIR/shr_frozen_violation1.rs:LL:CC +note: inside `foo` at $DIR/shr_frozen_violation1.rs:LL:CC + --> $DIR/shr_frozen_violation1.rs:LL:CC + | +LL | unknown_code(&*x); + | ^^^^^^^^^^^^^^^^^ +note: inside `main` at $DIR/shr_frozen_violation1.rs:LL:CC + --> $DIR/shr_frozen_violation1.rs:LL:CC + | +LL | println!("{}", foo(&mut 0)); + | ^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/static_memory_modification.rs b/src/tools/miri/tests/fail/stacked_borrows/static_memory_modification.rs new file mode 100644 index 0000000000000..84d7878b264e5 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/static_memory_modification.rs @@ -0,0 +1,8 @@ +static X: usize = 5; + +#[allow(mutable_transmutes)] +fn main() { + let _x = unsafe { + std::mem::transmute::<&usize, &mut usize>(&X) //~ ERROR: writing to alloc1 which is read-only + }; +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/static_memory_modification.stderr b/src/tools/miri/tests/fail/stacked_borrows/static_memory_modification.stderr new file mode 100644 index 0000000000000..ca99a8262b8bd --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/static_memory_modification.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: writing to ALLOC which is read-only + --> $DIR/static_memory_modification.rs:LL:CC + | +LL | std::mem::transmute::<&usize, &mut usize>(&X) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ writing to ALLOC which is read-only + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/static_memory_modification.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/track_caller.rs b/src/tools/miri/tests/fail/stacked_borrows/track_caller.rs new file mode 100644 index 0000000000000..3455eb4684ea2 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/track_caller.rs @@ -0,0 +1,17 @@ +// This is a copy of illegal_read1.rs, but with #[track_caller] on the test. +// This test only checks that our diagnostics do not display the contents of callee. + +#[rustfmt::skip] // rustfmt bug: https://github.com/rust-lang/rustfmt/issues/5391 +fn main() { + let mut x = 15; + let xraw = &mut x as *mut _; + let xref = unsafe { &mut *xraw }; // derived from raw, so using raw is still ok... + callee(xraw); + let _val = *xref; // ...but any use of raw will invalidate our ref. + //~^ ERROR: /read access .* tag does not exist in the borrow stack/ +} + +#[track_caller] +fn callee(xraw: *mut i32) { + let _val = unsafe { *xraw }; +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/track_caller.stderr b/src/tools/miri/tests/fail/stacked_borrows/track_caller.stderr new file mode 100644 index 0000000000000..6f1d0ccd348ec --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/track_caller.stderr @@ -0,0 +1,28 @@ +error: Undefined Behavior: attempting a read access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + --> $DIR/track_caller.rs:LL:CC + | +LL | let _val = *xref; // ...but any use of raw will invalidate our ref. + | ^^^^^ + | | + | attempting a read access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + | this error occurs as part of an access at ALLOC[0x0..0x4] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a Unique retag at offsets [0x0..0x4] + --> $DIR/track_caller.rs:LL:CC + | +LL | let xref = unsafe { &mut *xraw }; // derived from raw, so using raw is still ok... + | ^^^^^^^^^^ +help: was later invalidated at offsets [0x0..0x4] by a read access + --> $DIR/track_caller.rs:LL:CC + | +LL | callee(xraw); + | ^^^^^^^^^^^^ + = note: BACKTRACE: + = note: inside `main` at $DIR/track_caller.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/transmute-is-no-escape.rs b/src/tools/miri/tests/fail/stacked_borrows/transmute-is-no-escape.rs new file mode 100644 index 0000000000000..233b927dfc7ee --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/transmute-is-no-escape.rs @@ -0,0 +1,14 @@ +// Make sure we cannot use raw ptrs that got transmuted from mutable references +// (i.e, no EscapeToRaw happened). +// We could, in principle, do EscapeToRaw lazily to allow this code, but that +// would no alleviate the need for EscapeToRaw (see `ref_raw_int_raw` in +// `run-pass/stacked-borrows.rs`), and thus increase overall complexity. +use std::mem; + +fn main() { + let mut x: [i32; 2] = [42, 43]; + let _raw: *mut i32 = unsafe { mem::transmute(&mut x[0]) }; + // `raw` still carries a tag, so we get another pointer to the same location that does not carry a tag + let raw = (&mut x[1] as *mut i32).wrapping_offset(-1); + unsafe { *raw = 13 }; //~ ERROR: /write access .* tag does not exist in the borrow stack/ +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/transmute-is-no-escape.stderr b/src/tools/miri/tests/fail/stacked_borrows/transmute-is-no-escape.stderr new file mode 100644 index 0000000000000..a2ecb07fd3117 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/transmute-is-no-escape.stderr @@ -0,0 +1,23 @@ +error: Undefined Behavior: attempting a write access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + --> $DIR/transmute-is-no-escape.rs:LL:CC + | +LL | unsafe { *raw = 13 }; + | ^^^^^^^^^ + | | + | attempting a write access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + | this error occurs as part of an access at ALLOC[0x0..0x4] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a SharedReadWrite retag at offsets [0x4..0x8] + --> $DIR/transmute-is-no-escape.rs:LL:CC + | +LL | let raw = (&mut x[1] as *mut i32).wrapping_offset(-1); + | ^^^^^^^^^ + = note: BACKTRACE: + = note: inside `main` at $DIR/transmute-is-no-escape.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/unescaped_local.rs b/src/tools/miri/tests/fail/stacked_borrows/unescaped_local.rs new file mode 100644 index 0000000000000..49c0e66d85701 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/unescaped_local.rs @@ -0,0 +1,12 @@ +//@compile-flags: -Zmiri-permissive-provenance + +// Make sure we cannot use raw ptrs to access a local that +// we took the direct address of. +fn main() { + let mut x = 42; + let raw = &mut x as *mut i32 as usize as *mut i32; + let _ptr = &mut x; + unsafe { + *raw = 13; //~ ERROR: /write access .* no exposed tags/ + } +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/unescaped_local.stderr b/src/tools/miri/tests/fail/stacked_borrows/unescaped_local.stderr new file mode 100644 index 0000000000000..4deafa890005b --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/unescaped_local.stderr @@ -0,0 +1,18 @@ +error: Undefined Behavior: attempting a write access using at ALLOC[0x0], but no exposed tags have suitable permission in the borrow stack for this location + --> $DIR/unescaped_local.rs:LL:CC + | +LL | *raw = 13; + | ^^^^^^^^^ + | | + | attempting a write access using at ALLOC[0x0], but no exposed tags have suitable permission in the borrow stack for this location + | this error occurs as part of an access at ALLOC[0x0..0x4] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/unescaped_local.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/unescaped_static.rs b/src/tools/miri/tests/fail/stacked_borrows/unescaped_static.rs new file mode 100644 index 0000000000000..a25d9b5162ec8 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/unescaped_static.rs @@ -0,0 +1,7 @@ +static ARRAY: [u8; 2] = [0, 1]; + +fn main() { + let ptr_to_first = &ARRAY[0] as *const u8; + // Illegally use this to access the 2nd element. + let _val = unsafe { *ptr_to_first.add(1) }; //~ ERROR: /read access .* tag does not exist in the borrow stack/ +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/unescaped_static.stderr b/src/tools/miri/tests/fail/stacked_borrows/unescaped_static.stderr new file mode 100644 index 0000000000000..01a4bf4340c78 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/unescaped_static.stderr @@ -0,0 +1,23 @@ +error: Undefined Behavior: attempting a read access using at ALLOC[0x1], but that tag does not exist in the borrow stack for this location + --> $DIR/unescaped_static.rs:LL:CC + | +LL | let _val = unsafe { *ptr_to_first.add(1) }; + | ^^^^^^^^^^^^^^^^^^^^ + | | + | attempting a read access using at ALLOC[0x1], but that tag does not exist in the borrow stack for this location + | this error occurs as part of an access at ALLOC[0x1..0x2] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a SharedReadOnly retag at offsets [0x0..0x1] + --> $DIR/unescaped_static.rs:LL:CC + | +LL | let ptr_to_first = &ARRAY[0] as *const u8; + | ^^^^^^^^^ + = note: BACKTRACE: + = note: inside `main` at $DIR/unescaped_static.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/zst_slice.rs b/src/tools/miri/tests/fail/stacked_borrows/zst_slice.rs new file mode 100644 index 0000000000000..77daa9c9811ae --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/zst_slice.rs @@ -0,0 +1,11 @@ +//@compile-flags: -Zmiri-strict-provenance +//@error-pattern: /retag .* tag does not exist in the borrow stack/ + +fn main() { + unsafe { + let a = [1, 2, 3]; + let s = &a[0..0]; + assert_eq!(s.len(), 0); + assert_eq!(*s.get_unchecked(1), 2); + } +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/zst_slice.stderr b/src/tools/miri/tests/fail/stacked_borrows/zst_slice.stderr new file mode 100644 index 0000000000000..86f1da1f70a33 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/zst_slice.stderr @@ -0,0 +1,28 @@ +error: Undefined Behavior: trying to retag from for SharedReadOnly permission at ALLOC[0x4], but that tag does not exist in the borrow stack for this location + --> RUSTLIB/core/src/slice/mod.rs:LL:CC + | +LL | unsafe { &*index.get_unchecked(self) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | trying to retag from for SharedReadOnly permission at ALLOC[0x4], but that tag does not exist in the borrow stack for this location + | this error occurs as part of retag at ALLOC[0x4..0x8] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: would have been created here, but this is a zero-size retag ([0x0..0x0]) so the tag in question does not exist anywhere + --> $DIR/zst_slice.rs:LL:CC + | +LL | assert_eq!(*s.get_unchecked(1), 2); + | ^^^^^^^^^^^^^^^^^^ + = note: BACKTRACE: + = note: inside `core::slice::::get_unchecked::` at RUSTLIB/core/src/slice/mod.rs:LL:CC +note: inside `main` at $DIR/zst_slice.rs:LL:CC + --> $DIR/zst_slice.rs:LL:CC + | +LL | assert_eq!(*s.get_unchecked(1), 2); + | ^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/static_memory_modification1.rs b/src/tools/miri/tests/fail/static_memory_modification1.rs new file mode 100644 index 0000000000000..66794e7535a45 --- /dev/null +++ b/src/tools/miri/tests/fail/static_memory_modification1.rs @@ -0,0 +1,12 @@ +// Stacked Borrows detects that we are casting & to &mut and so it changes why we fail +//@compile-flags: -Zmiri-disable-stacked-borrows + +static X: usize = 5; + +#[allow(mutable_transmutes)] +fn main() { + unsafe { + *std::mem::transmute::<&usize, &mut usize>(&X) = 6; //~ ERROR: read-only + assert_eq!(X, 6); + } +} diff --git a/src/tools/miri/tests/fail/static_memory_modification1.stderr b/src/tools/miri/tests/fail/static_memory_modification1.stderr new file mode 100644 index 0000000000000..5e7213ee6088e --- /dev/null +++ b/src/tools/miri/tests/fail/static_memory_modification1.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: writing to ALLOC which is read-only + --> $DIR/static_memory_modification1.rs:LL:CC + | +LL | *std::mem::transmute::<&usize, &mut usize>(&X) = 6; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ writing to ALLOC which is read-only + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/static_memory_modification1.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/static_memory_modification2.rs b/src/tools/miri/tests/fail/static_memory_modification2.rs new file mode 100644 index 0000000000000..d8ae3a57c51e3 --- /dev/null +++ b/src/tools/miri/tests/fail/static_memory_modification2.rs @@ -0,0 +1,12 @@ +// Stacked Borrows detects that we are casting & to &mut and so it changes why we fail +//@compile-flags: -Zmiri-disable-stacked-borrows + +use std::mem::transmute; + +#[allow(mutable_transmutes)] +fn main() { + unsafe { + let s = "this is a test"; + transmute::<&[u8], &mut [u8]>(s.as_bytes())[4] = 42; //~ ERROR: read-only + } +} diff --git a/src/tools/miri/tests/fail/static_memory_modification2.stderr b/src/tools/miri/tests/fail/static_memory_modification2.stderr new file mode 100644 index 0000000000000..4c160cd320688 --- /dev/null +++ b/src/tools/miri/tests/fail/static_memory_modification2.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: writing to ALLOC which is read-only + --> $DIR/static_memory_modification2.rs:LL:CC + | +LL | transmute::<&[u8], &mut [u8]>(s.as_bytes())[4] = 42; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ writing to ALLOC which is read-only + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/static_memory_modification2.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/static_memory_modification3.rs b/src/tools/miri/tests/fail/static_memory_modification3.rs new file mode 100644 index 0000000000000..b8e2c6470ff20 --- /dev/null +++ b/src/tools/miri/tests/fail/static_memory_modification3.rs @@ -0,0 +1,12 @@ +// Stacked Borrows detects that we are casting & to &mut and so it changes why we fail +//@compile-flags: -Zmiri-disable-stacked-borrows + +use std::mem::transmute; + +#[allow(mutable_transmutes)] +fn main() { + unsafe { + let bs = b"this is a test"; + transmute::<&[u8], &mut [u8]>(bs)[4] = 42; //~ ERROR: read-only + } +} diff --git a/src/tools/miri/tests/fail/static_memory_modification3.stderr b/src/tools/miri/tests/fail/static_memory_modification3.stderr new file mode 100644 index 0000000000000..1986059c50a8e --- /dev/null +++ b/src/tools/miri/tests/fail/static_memory_modification3.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: writing to ALLOC which is read-only + --> $DIR/static_memory_modification3.rs:LL:CC + | +LL | transmute::<&[u8], &mut [u8]>(bs)[4] = 42; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ writing to ALLOC which is read-only + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/static_memory_modification3.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/transmute-pair-uninit.rs b/src/tools/miri/tests/fail/transmute-pair-uninit.rs new file mode 100644 index 0000000000000..bc95f3cb7ad3a --- /dev/null +++ b/src/tools/miri/tests/fail/transmute-pair-uninit.rs @@ -0,0 +1,24 @@ +#![feature(core_intrinsics)] + +use std::mem; + +fn main() { + let x: Option> = unsafe { + let z = std::intrinsics::add_with_overflow(0usize, 0usize); + std::mem::transmute::<(usize, bool), Option>>(z) + }; + let y = &x; + // Now read this bytewise. There should be (`ptr_size + 1`) def bytes followed by + // (`ptr_size - 1`) undef bytes (the padding after the bool) in there. + let z: *const u8 = y as *const _ as *const _; + let first_undef = mem::size_of::() as isize + 1; + for i in 0..first_undef { + let byte = unsafe { *z.offset(i) }; + assert_eq!(byte, 0); + } + let v = unsafe { *z.offset(first_undef) }; + //~^ ERROR: uninitialized + if v == 0 { + println!("it is zero"); + } +} diff --git a/src/tools/miri/tests/fail/transmute-pair-uninit.stderr b/src/tools/miri/tests/fail/transmute-pair-uninit.stderr new file mode 100644 index 0000000000000..642bf0a713436 --- /dev/null +++ b/src/tools/miri/tests/fail/transmute-pair-uninit.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: using uninitialized data, but this operation requires initialized memory + --> $DIR/transmute-pair-uninit.rs:LL:CC + | +LL | let v = unsafe { *z.offset(first_undef) }; + | ^^^^^^^^^^^^^^^^^^^^^^ using uninitialized data, but this operation requires initialized memory + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/transmute-pair-uninit.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/type-too-large.rs b/src/tools/miri/tests/fail/type-too-large.rs new file mode 100644 index 0000000000000..21b272f8ec398 --- /dev/null +++ b/src/tools/miri/tests/fail/type-too-large.rs @@ -0,0 +1,6 @@ +//@ignore-32bit + +fn main() { + let _fat: [u8; (1 << 61) + (1 << 31)]; + _fat = [0; (1u64 << 61) as usize + (1u64 << 31) as usize]; //~ ERROR: post-monomorphization error +} diff --git a/src/tools/miri/tests/fail/type-too-large.stderr b/src/tools/miri/tests/fail/type-too-large.stderr new file mode 100644 index 0000000000000..cb1d725ec878c --- /dev/null +++ b/src/tools/miri/tests/fail/type-too-large.stderr @@ -0,0 +1,12 @@ +error: post-monomorphization error: values of the type `[u8; 2305843011361177600]` are too big for the current architecture + --> $DIR/type-too-large.rs:LL:CC + | +LL | _fat = [0; (1u64 << 61) as usize + (1u64 << 31) as usize]; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ values of the type `[u8; 2305843011361177600]` are too big for the current architecture + | + = note: inside `main` at $DIR/type-too-large.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/unaligned_pointers/alignment.rs b/src/tools/miri/tests/fail/unaligned_pointers/alignment.rs new file mode 100644 index 0000000000000..438e74e5b8d52 --- /dev/null +++ b/src/tools/miri/tests/fail/unaligned_pointers/alignment.rs @@ -0,0 +1,14 @@ +//@normalize-stderr-test: "\| +\^+" -> "| ^" + +fn main() { + // No retry needed, this fails reliably. + + let mut x = [0u8; 20]; + let x_ptr: *mut u8 = x.as_mut_ptr(); + #[rustfmt::skip] + unsafe { + // At least one of these is definitely unaligned. + *(x_ptr as *mut u32) = 42; *(x_ptr.add(1) as *mut u32) = 42; + //~^ ERROR: but alignment 4 is required + }; +} diff --git a/src/tools/miri/tests/fail/unaligned_pointers/alignment.stderr b/src/tools/miri/tests/fail/unaligned_pointers/alignment.stderr new file mode 100644 index 0000000000000..bbebe3b89fd7e --- /dev/null +++ b/src/tools/miri/tests/fail/unaligned_pointers/alignment.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: accessing memory with alignment ALIGN, but alignment ALIGN is required + --> $DIR/alignment.rs:LL:CC + | +LL | *(x_ptr as *mut u32) = 42; *(x_ptr.add(1) as *mut u32) = 42; + | ^ accessing memory with alignment ALIGN, but alignment ALIGN is required + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/alignment.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/unaligned_pointers/atomic_unaligned.rs b/src/tools/miri/tests/fail/unaligned_pointers/atomic_unaligned.rs new file mode 100644 index 0000000000000..9dd652fd8217a --- /dev/null +++ b/src/tools/miri/tests/fail/unaligned_pointers/atomic_unaligned.rs @@ -0,0 +1,13 @@ +//@compile-flags: -Zmiri-symbolic-alignment-check +#![feature(core_intrinsics)] + +fn main() { + // Do a 4-aligned u64 atomic access. That should be UB on all platforms, + // even if u64 only has alignment 4. + let z = [0u32; 2]; + let zptr = &z as *const _ as *const u64; + unsafe { + ::std::intrinsics::atomic_load_seqcst(zptr); + //~^ERROR: accessing memory with alignment 4, but alignment 8 is required + } +} diff --git a/src/tools/miri/tests/fail/unaligned_pointers/atomic_unaligned.stderr b/src/tools/miri/tests/fail/unaligned_pointers/atomic_unaligned.stderr new file mode 100644 index 0000000000000..8c3aa3429af5f --- /dev/null +++ b/src/tools/miri/tests/fail/unaligned_pointers/atomic_unaligned.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: accessing memory with alignment ALIGN, but alignment ALIGN is required + --> $DIR/atomic_unaligned.rs:LL:CC + | +LL | ::std::intrinsics::atomic_load_seqcst(zptr); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ accessing memory with alignment ALIGN, but alignment ALIGN is required + | + = help: this usually indicates that your program performed an invalid operation and caused Undefined Behavior + = help: but due to `-Zmiri-symbolic-alignment-check`, alignment errors can also be false positives + = note: BACKTRACE: + = note: inside `main` at $DIR/atomic_unaligned.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/unaligned_pointers/dyn_alignment.rs b/src/tools/miri/tests/fail/unaligned_pointers/dyn_alignment.rs new file mode 100644 index 0000000000000..b943c7db7ccd6 --- /dev/null +++ b/src/tools/miri/tests/fail/unaligned_pointers/dyn_alignment.rs @@ -0,0 +1,24 @@ +// should find the bug even without validation and stacked borrows, but gets masked by optimizations +//@compile-flags: -Zmiri-disable-validation -Zmiri-disable-stacked-borrows -Zmir-opt-level=0 + +#[repr(align(256))] +#[derive(Debug)] +struct MuchAlign; + +fn main() { + // Try many times as this might work by chance. + for _ in 0..10 { + let buf = [0u32; 256]; + // `buf` is sufficiently aligned for `layout.align` on a `dyn Debug`, but not + // for the actual alignment required by `MuchAlign`. + // We craft a wide reference `&dyn Debug` with the vtable for `MuchAlign`. That should be UB, + // as the reference is not aligned to its dynamic alignment requirements. + let mut ptr = &MuchAlign as &dyn std::fmt::Debug; + // Overwrite the data part of `ptr` so it points to `buf`. + unsafe { + (&mut ptr as *mut _ as *mut *const u8).write(&buf as *const _ as *const u8); + } + // Re-borrow that. This should be UB. + let _ptr = &*ptr; //~ERROR: alignment 256 is required + } +} diff --git a/src/tools/miri/tests/fail/unaligned_pointers/dyn_alignment.stderr b/src/tools/miri/tests/fail/unaligned_pointers/dyn_alignment.stderr new file mode 100644 index 0000000000000..a900b46612b8a --- /dev/null +++ b/src/tools/miri/tests/fail/unaligned_pointers/dyn_alignment.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: accessing memory with alignment ALIGN, but alignment ALIGN is required + --> $DIR/dyn_alignment.rs:LL:CC + | +LL | let _ptr = &*ptr; + | ^^^^^ accessing memory with alignment ALIGN, but alignment ALIGN is required + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/dyn_alignment.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/unaligned_pointers/intptrcast_alignment_check.rs b/src/tools/miri/tests/fail/unaligned_pointers/intptrcast_alignment_check.rs new file mode 100644 index 0000000000000..da4cadc1c8763 --- /dev/null +++ b/src/tools/miri/tests/fail/unaligned_pointers/intptrcast_alignment_check.rs @@ -0,0 +1,17 @@ +//@compile-flags: -Zmiri-symbolic-alignment-check -Zmiri-permissive-provenance +// With the symbolic alignment check, even with intptrcast and without +// validation, we want to be *sure* to catch bugs that arise from pointers being +// insufficiently aligned. The only way to achieve that is not not let programs +// exploit integer information for alignment, so here we test that this is +// indeed the case. +// +// See https://github.com/rust-lang/miri/issues/1074. +fn main() { + let x = &mut [0u8; 3]; + let base_addr = x as *mut _ as usize; + // Manually make sure the pointer is properly aligned. + let base_addr_aligned = if base_addr % 2 == 0 { base_addr } else { base_addr + 1 }; + let u16_ptr = base_addr_aligned as *mut u16; + unsafe { *u16_ptr = 2 }; //~ERROR: memory with alignment 1, but alignment 2 is required + println!("{:?}", x); +} diff --git a/src/tools/miri/tests/fail/unaligned_pointers/intptrcast_alignment_check.stderr b/src/tools/miri/tests/fail/unaligned_pointers/intptrcast_alignment_check.stderr new file mode 100644 index 0000000000000..392495a386de7 --- /dev/null +++ b/src/tools/miri/tests/fail/unaligned_pointers/intptrcast_alignment_check.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: accessing memory with alignment ALIGN, but alignment ALIGN is required + --> $DIR/intptrcast_alignment_check.rs:LL:CC + | +LL | unsafe { *u16_ptr = 2 }; + | ^^^^^^^^^^^^ accessing memory with alignment ALIGN, but alignment ALIGN is required + | + = help: this usually indicates that your program performed an invalid operation and caused Undefined Behavior + = help: but due to `-Zmiri-symbolic-alignment-check`, alignment errors can also be false positives + = note: BACKTRACE: + = note: inside `main` at $DIR/intptrcast_alignment_check.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/unaligned_pointers/reference_to_packed.rs b/src/tools/miri/tests/fail/unaligned_pointers/reference_to_packed.rs new file mode 100644 index 0000000000000..752210dca46e8 --- /dev/null +++ b/src/tools/miri/tests/fail/unaligned_pointers/reference_to_packed.rs @@ -0,0 +1,19 @@ +// This should fail even without validation/SB +//@compile-flags: -Zmiri-disable-validation -Zmiri-disable-stacked-borrows + +#![allow(dead_code, unused_variables, unaligned_references)] + +#[repr(packed)] +struct Foo { + x: i32, + y: i32, +} + +fn main() { + // Try many times as this might work by chance. + for _ in 0..10 { + let foo = Foo { x: 42, y: 99 }; + let p = &foo.x; + let i = *p; //~ERROR: alignment 4 is required + } +} diff --git a/src/tools/miri/tests/fail/unaligned_pointers/reference_to_packed.stderr b/src/tools/miri/tests/fail/unaligned_pointers/reference_to_packed.stderr new file mode 100644 index 0000000000000..6c2a3dca2deef --- /dev/null +++ b/src/tools/miri/tests/fail/unaligned_pointers/reference_to_packed.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: accessing memory with alignment ALIGN, but alignment ALIGN is required + --> $DIR/reference_to_packed.rs:LL:CC + | +LL | let i = *p; + | ^^ accessing memory with alignment ALIGN, but alignment ALIGN is required + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/reference_to_packed.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr1.rs b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr1.rs new file mode 100644 index 0000000000000..73adc4dc44916 --- /dev/null +++ b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr1.rs @@ -0,0 +1,12 @@ +// This should fail even without validation or Stacked Borrows. +//@compile-flags: -Zmiri-disable-validation -Zmiri-disable-stacked-borrows + +fn main() { + // Try many times as this might work by chance. + for _ in 0..10 { + let x = [2u16, 3, 4]; // Make it big enough so we don't get an out-of-bounds error. + let x = &x[0] as *const _ as *const u32; + // This must fail because alignment is violated: the allocation's base is not sufficiently aligned. + let _x = unsafe { *x }; //~ERROR: memory with alignment 2, but alignment 4 is required + } +} diff --git a/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr1.stderr b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr1.stderr new file mode 100644 index 0000000000000..49292be9cd158 --- /dev/null +++ b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr1.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: accessing memory with alignment ALIGN, but alignment ALIGN is required + --> $DIR/unaligned_ptr1.rs:LL:CC + | +LL | let _x = unsafe { *x }; + | ^^ accessing memory with alignment ALIGN, but alignment ALIGN is required + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/unaligned_ptr1.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr2.rs b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr2.rs new file mode 100644 index 0000000000000..c252944ffb7b7 --- /dev/null +++ b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr2.rs @@ -0,0 +1,12 @@ +// This should fail even without validation or Stacked Borrows. +//@compile-flags: -Zmiri-disable-validation -Zmiri-disable-stacked-borrows + +fn main() { + // No retry needed, this fails reliably. + + let x = [2u32, 3]; // Make it big enough so we don't get an out-of-bounds error. + let x = (x.as_ptr() as *const u8).wrapping_offset(3) as *const u32; + // This must fail because alignment is violated: the offset is not sufficiently aligned. + // Also make the offset not a power of 2, that used to ICE. + let _x = unsafe { *x }; //~ERROR: memory with alignment 1, but alignment 4 is required +} diff --git a/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr2.stderr b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr2.stderr new file mode 100644 index 0000000000000..e75482f723b69 --- /dev/null +++ b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr2.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: accessing memory with alignment ALIGN, but alignment ALIGN is required + --> $DIR/unaligned_ptr2.rs:LL:CC + | +LL | let _x = unsafe { *x }; + | ^^ accessing memory with alignment ALIGN, but alignment ALIGN is required + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/unaligned_ptr2.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr3.rs b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr3.rs new file mode 100644 index 0000000000000..7605dd175a982 --- /dev/null +++ b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr3.rs @@ -0,0 +1,13 @@ +// This should fail even without validation or Stacked Borrows. +//@compile-flags: -Zmiri-disable-validation -Zmiri-disable-stacked-borrows + +fn main() { + // Try many times as this might work by chance. + for _ in 0..10 { + let x = [2u16, 3, 4, 5]; // Make it big enough so we don't get an out-of-bounds error. + let x = &x[0] as *const _ as *const *const u8; // cast to ptr-to-ptr, so that we load a ptr + // This must fail because alignment is violated. Test specifically for loading pointers, + // which have special code in miri's memory. + let _x = unsafe { *x }; //~ERROR: but alignment + } +} diff --git a/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr3.stderr b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr3.stderr new file mode 100644 index 0000000000000..50dd4fdfc89f5 --- /dev/null +++ b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr3.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: accessing memory with alignment ALIGN, but alignment ALIGN is required + --> $DIR/unaligned_ptr3.rs:LL:CC + | +LL | let _x = unsafe { *x }; + | ^^ accessing memory with alignment ALIGN, but alignment ALIGN is required + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/unaligned_ptr3.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr4.rs b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr4.rs new file mode 100644 index 0000000000000..852febe4c04cc --- /dev/null +++ b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr4.rs @@ -0,0 +1,14 @@ +// This should fail even without validation or Stacked Borrows. +//@compile-flags: -Zmiri-disable-validation -Zmiri-disable-stacked-borrows + +fn main() { + // Make sure we notice when a u16 is loaded at offset 1 into a u8 allocation. + // (This would be missed if u8 allocations are *always* at odd addresses.) + // + // Try many times as this might work by chance. + for _ in 0..10 { + let x = [0u8; 4]; + let ptr = x.as_ptr().wrapping_offset(1).cast::(); + let _val = unsafe { *ptr }; //~ERROR: but alignment + } +} diff --git a/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr4.stderr b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr4.stderr new file mode 100644 index 0000000000000..182f3e0f876f5 --- /dev/null +++ b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr4.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: accessing memory with alignment ALIGN, but alignment ALIGN is required + --> $DIR/unaligned_ptr4.rs:LL:CC + | +LL | let _val = unsafe { *ptr }; + | ^^^^ accessing memory with alignment ALIGN, but alignment ALIGN is required + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/unaligned_ptr4.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr_addr_of.rs b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr_addr_of.rs new file mode 100644 index 0000000000000..e439cf2b03b96 --- /dev/null +++ b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr_addr_of.rs @@ -0,0 +1,14 @@ +// This should fail even without validation or Stacked Borrows. +//@compile-flags: -Zmiri-disable-validation -Zmiri-disable-stacked-borrows +use std::ptr; + +fn main() { + // Try many times as this might work by chance. + for _ in 0..10 { + let x = [2u16, 3, 4]; // Make it big enough so we don't get an out-of-bounds error. + let x = &x[0] as *const _ as *const u32; + // This must fail because alignment is violated: the allocation's base is not sufficiently aligned. + // The deref is UB even if we just put the result into a raw pointer. + let _x = unsafe { ptr::addr_of!(*x) }; //~ ERROR: memory with alignment 2, but alignment 4 is required + } +} diff --git a/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr_addr_of.stderr b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr_addr_of.stderr new file mode 100644 index 0000000000000..2d8b1bf74508a --- /dev/null +++ b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr_addr_of.stderr @@ -0,0 +1,16 @@ +error: Undefined Behavior: accessing memory with alignment ALIGN, but alignment ALIGN is required + --> $DIR/unaligned_ptr_addr_of.rs:LL:CC + | +LL | let _x = unsafe { ptr::addr_of!(*x) }; + | ^^^^^^^^^^^^^^^^^ accessing memory with alignment ALIGN, but alignment ALIGN is required + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at RUSTLIB/core/src/ptr/mod.rs:LL:CC + = note: this error originates in the macro `ptr::addr_of` (in Nightly builds, run with -Z macro-backtrace for more info) + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr_zst.rs b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr_zst.rs new file mode 100644 index 0000000000000..9076581b55bbc --- /dev/null +++ b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr_zst.rs @@ -0,0 +1,13 @@ +// This should fail even without validation +// Some optimizations remove ZST accesses, thus masking this UB. +//@compile-flags: -Zmir-opt-level=0 -Zmiri-disable-validation + +fn main() { + // Try many times as this might work by chance. + for i in 0..10 { + let x = i as u8; + let x = &x as *const _ as *const [u32; 0]; + // This must fail because alignment is violated. Test specifically for loading ZST. + let _x = unsafe { *x }; //~ERROR: alignment 4 is required + } +} diff --git a/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr_zst.stderr b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr_zst.stderr new file mode 100644 index 0000000000000..aa0cbe1623b6e --- /dev/null +++ b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr_zst.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: accessing memory with alignment ALIGN, but alignment ALIGN is required + --> $DIR/unaligned_ptr_zst.rs:LL:CC + | +LL | let _x = unsafe { *x }; + | ^^ accessing memory with alignment ALIGN, but alignment ALIGN is required + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/unaligned_ptr_zst.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/uninit_buffer.rs b/src/tools/miri/tests/fail/uninit_buffer.rs new file mode 100644 index 0000000000000..d21371225e571 --- /dev/null +++ b/src/tools/miri/tests/fail/uninit_buffer.rs @@ -0,0 +1,20 @@ +//@error-pattern: memory is uninitialized at [0x4..0x10] + +use std::alloc::{alloc, dealloc, Layout}; +use std::slice::from_raw_parts; + +fn main() { + let layout = Layout::from_size_align(32, 8).unwrap(); + unsafe { + let ptr = alloc(layout); + *ptr = 0x41; + *ptr.add(1) = 0x42; + *ptr.add(2) = 0x43; + *ptr.add(3) = 0x44; + *ptr.add(16) = 0x00; + let slice1 = from_raw_parts(ptr, 16); + let slice2 = from_raw_parts(ptr.add(16), 16); + drop(slice1.cmp(slice2)); + dealloc(ptr, layout); + } +} diff --git a/src/tools/miri/tests/fail/uninit_buffer.stderr b/src/tools/miri/tests/fail/uninit_buffer.stderr new file mode 100644 index 0000000000000..a543d59addb14 --- /dev/null +++ b/src/tools/miri/tests/fail/uninit_buffer.stderr @@ -0,0 +1,27 @@ +error: Undefined Behavior: reading memory at ALLOC[0x0..0x10], but memory is uninitialized at [0x4..0x10], and this operation requires initialized memory + --> RUSTLIB/core/src/slice/cmp.rs:LL:CC + | +LL | let mut order = unsafe { memcmp(left.as_ptr(), right.as_ptr(), len) as isize }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ reading memory at ALLOC[0x0..0x10], but memory is uninitialized at [0x4..0x10], and this operation requires initialized memory + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `::compare` at RUSTLIB/core/src/slice/cmp.rs:LL:CC + = note: inside `core::slice::cmp::::cmp` at RUSTLIB/core/src/slice/cmp.rs:LL:CC +note: inside `main` at $DIR/uninit_buffer.rs:LL:CC + --> $DIR/uninit_buffer.rs:LL:CC + | +LL | drop(slice1.cmp(slice2)); + | ^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +Uninitialized memory occurred at ALLOC[0x4..0x10], in this allocation: +ALLOC (Rust heap, size: 32, align: 8) { + 0x00 │ 41 42 43 44 __ __ __ __ __ __ __ __ __ __ __ __ │ ABCD░░░░░░░░░░░░ + 0x10 │ 00 __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ │ .░░░░░░░░░░░░░░░ +} + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/uninit_byte_read.rs b/src/tools/miri/tests/fail/uninit_byte_read.rs new file mode 100644 index 0000000000000..f1dace0cff9b1 --- /dev/null +++ b/src/tools/miri/tests/fail/uninit_byte_read.rs @@ -0,0 +1,7 @@ +//@compile-flags: -Zmiri-disable-stacked-borrows +fn main() { + let v: Vec = Vec::with_capacity(10); + let undef = unsafe { *v.get_unchecked(5) }; //~ ERROR: uninitialized + let x = undef + 1; + panic!("this should never print: {}", x); +} diff --git a/src/tools/miri/tests/fail/uninit_byte_read.stderr b/src/tools/miri/tests/fail/uninit_byte_read.stderr new file mode 100644 index 0000000000000..9f7638888d643 --- /dev/null +++ b/src/tools/miri/tests/fail/uninit_byte_read.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: using uninitialized data, but this operation requires initialized memory + --> $DIR/uninit_byte_read.rs:LL:CC + | +LL | let undef = unsafe { *v.get_unchecked(5) }; + | ^^^^^^^^^^^^^^^^^^^ using uninitialized data, but this operation requires initialized memory + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/uninit_byte_read.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/unreachable.rs b/src/tools/miri/tests/fail/unreachable.rs new file mode 100644 index 0000000000000..3389d5b9ddeaf --- /dev/null +++ b/src/tools/miri/tests/fail/unreachable.rs @@ -0,0 +1,3 @@ +fn main() { + unsafe { std::hint::unreachable_unchecked() } //~ERROR: entering unreachable code +} diff --git a/src/tools/miri/tests/fail/unreachable.stderr b/src/tools/miri/tests/fail/unreachable.stderr new file mode 100644 index 0000000000000..a57d731f1f713 --- /dev/null +++ b/src/tools/miri/tests/fail/unreachable.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: entering unreachable code + --> $DIR/unreachable.rs:LL:CC + | +LL | unsafe { std::hint::unreachable_unchecked() } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ entering unreachable code + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/unreachable.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/unsized-local.rs b/src/tools/miri/tests/fail/unsized-local.rs new file mode 100644 index 0000000000000..ceccae4e3e76f --- /dev/null +++ b/src/tools/miri/tests/fail/unsized-local.rs @@ -0,0 +1,23 @@ +#![feature(unsized_locals)] +#![allow(incomplete_features)] + +fn main() { + pub trait Foo { + fn foo(self) -> String; + } + + struct A; + + impl Foo for A { + fn foo(self) -> String { + format!("hello") + } + } + + let x = *(Box::new(A) as Box); //~ERROR: unsized locals are not supported + assert_eq!(x.foo(), format!("hello")); + + // I'm not sure whether we want this to work + let x = Box::new(A) as Box; + assert_eq!(x.foo(), format!("hello")); +} diff --git a/src/tools/miri/tests/fail/unsized-local.stderr b/src/tools/miri/tests/fail/unsized-local.stderr new file mode 100644 index 0000000000000..66d93c6f503cb --- /dev/null +++ b/src/tools/miri/tests/fail/unsized-local.stderr @@ -0,0 +1,14 @@ +error: unsupported operation: unsized locals are not supported + --> $DIR/unsized-local.rs:LL:CC + | +LL | let x = *(Box::new(A) as Box); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unsized locals are not supported + | + = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support + = note: BACKTRACE: + = note: inside `main` at $DIR/unsized-local.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/unsupported_foreign_function.rs b/src/tools/miri/tests/fail/unsupported_foreign_function.rs new file mode 100644 index 0000000000000..dfd099e734b99 --- /dev/null +++ b/src/tools/miri/tests/fail/unsupported_foreign_function.rs @@ -0,0 +1,9 @@ +fn main() { + extern "Rust" { + fn foo(); + } + + unsafe { + foo(); //~ ERROR: unsupported operation: can't call foreign function: foo + } +} diff --git a/src/tools/miri/tests/fail/unsupported_foreign_function.stderr b/src/tools/miri/tests/fail/unsupported_foreign_function.stderr new file mode 100644 index 0000000000000..fde5fb78ac08a --- /dev/null +++ b/src/tools/miri/tests/fail/unsupported_foreign_function.stderr @@ -0,0 +1,14 @@ +error: unsupported operation: can't call foreign function: foo + --> $DIR/unsupported_foreign_function.rs:LL:CC + | +LL | foo(); + | ^^^^^ can't call foreign function: foo + | + = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support + = note: BACKTRACE: + = note: inside `main` at $DIR/unsupported_foreign_function.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/unsupported_signal.rs b/src/tools/miri/tests/fail/unsupported_signal.rs new file mode 100644 index 0000000000000..d50041ffbd9de --- /dev/null +++ b/src/tools/miri/tests/fail/unsupported_signal.rs @@ -0,0 +1,10 @@ +//! `signal()` is special on Linux and macOS that it's only supported within libstd. +//! The implementation is not complete enough to permit user code to call it. +//@ignore-target-windows: No libc on Windows + +fn main() { + unsafe { + libc::signal(libc::SIGPIPE, libc::SIG_IGN); + //~^ ERROR: unsupported operation: can't call foreign function: signal + } +} diff --git a/src/tools/miri/tests/fail/unsupported_signal.stderr b/src/tools/miri/tests/fail/unsupported_signal.stderr new file mode 100644 index 0000000000000..d22ecbc11ff66 --- /dev/null +++ b/src/tools/miri/tests/fail/unsupported_signal.stderr @@ -0,0 +1,14 @@ +error: unsupported operation: can't call foreign function: signal + --> $DIR/unsupported_signal.rs:LL:CC + | +LL | libc::signal(libc::SIGPIPE, libc::SIG_IGN); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ can't call foreign function: signal + | + = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support + = note: BACKTRACE: + = note: inside `main` at $DIR/unsupported_signal.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/validity/cast_fn_ptr1.rs b/src/tools/miri/tests/fail/validity/cast_fn_ptr1.rs new file mode 100644 index 0000000000000..6ab73569c6347 --- /dev/null +++ b/src/tools/miri/tests/fail/validity/cast_fn_ptr1.rs @@ -0,0 +1,13 @@ +//@compile-flags: -Zmiri-permissive-provenance + +fn main() { + // Cast a function pointer such that on a call, the argument gets transmuted + // from raw ptr to reference. This is ABI-compatible, so it's not the call that + // should fail, but validation should. + fn f(_x: &i32) {} + + let g: fn(*const i32) = unsafe { std::mem::transmute(f as fn(&i32)) }; + + g(0usize as *const i32) + //~^ ERROR: encountered a null reference +} diff --git a/src/tools/miri/tests/fail/validity/cast_fn_ptr1.stderr b/src/tools/miri/tests/fail/validity/cast_fn_ptr1.stderr new file mode 100644 index 0000000000000..133e4b2c16a10 --- /dev/null +++ b/src/tools/miri/tests/fail/validity/cast_fn_ptr1.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: constructing invalid value: encountered a null reference + --> $DIR/cast_fn_ptr1.rs:LL:CC + | +LL | g(0usize as *const i32) + | ^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered a null reference + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/cast_fn_ptr1.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/validity/cast_fn_ptr2.rs b/src/tools/miri/tests/fail/validity/cast_fn_ptr2.rs new file mode 100644 index 0000000000000..64ddb563be538 --- /dev/null +++ b/src/tools/miri/tests/fail/validity/cast_fn_ptr2.rs @@ -0,0 +1,15 @@ +//@compile-flags: -Zmiri-permissive-provenance + +fn main() { + // Cast a function pointer such that when returning, the return value gets transmuted + // from raw ptr to reference. This is ABI-compatible, so it's not the call that + // should fail, but validation should. + fn f() -> *const i32 { + 0usize as *const i32 + } + + let g: fn() -> &'static i32 = unsafe { std::mem::transmute(f as fn() -> *const i32) }; + + let _x = g(); + //~^ ERROR: encountered a null reference +} diff --git a/src/tools/miri/tests/fail/validity/cast_fn_ptr2.stderr b/src/tools/miri/tests/fail/validity/cast_fn_ptr2.stderr new file mode 100644 index 0000000000000..21001f2b46096 --- /dev/null +++ b/src/tools/miri/tests/fail/validity/cast_fn_ptr2.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: constructing invalid value: encountered a null reference + --> $DIR/cast_fn_ptr2.rs:LL:CC + | +LL | let _x = g(); + | ^^^ constructing invalid value: encountered a null reference + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/cast_fn_ptr2.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/validity/dangling_ref1.rs b/src/tools/miri/tests/fail/validity/dangling_ref1.rs new file mode 100644 index 0000000000000..6bf2d9295a370 --- /dev/null +++ b/src/tools/miri/tests/fail/validity/dangling_ref1.rs @@ -0,0 +1,7 @@ +// Make sure we catch this even without Stacked Borrows +//@compile-flags: -Zmiri-disable-stacked-borrows +use std::mem; + +fn main() { + let _x: &i32 = unsafe { mem::transmute(16usize) }; //~ ERROR: encountered a dangling reference (address 0x10 is unallocated) +} diff --git a/src/tools/miri/tests/fail/validity/dangling_ref1.stderr b/src/tools/miri/tests/fail/validity/dangling_ref1.stderr new file mode 100644 index 0000000000000..01ef071e86930 --- /dev/null +++ b/src/tools/miri/tests/fail/validity/dangling_ref1.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: constructing invalid value: encountered a dangling reference (address 0x10 is unallocated) + --> $DIR/dangling_ref1.rs:LL:CC + | +LL | let _x: &i32 = unsafe { mem::transmute(16usize) }; + | ^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered a dangling reference (address 0x10 is unallocated) + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/dangling_ref1.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/validity/dangling_ref2.rs b/src/tools/miri/tests/fail/validity/dangling_ref2.rs new file mode 100644 index 0000000000000..77d2358ae7772 --- /dev/null +++ b/src/tools/miri/tests/fail/validity/dangling_ref2.rs @@ -0,0 +1,9 @@ +// Make sure we catch this even without Stacked Borrows +//@compile-flags: -Zmiri-disable-stacked-borrows +use std::mem; + +fn main() { + let val = 14; + let ptr = (&val as *const i32).wrapping_offset(1); + let _x: &i32 = unsafe { mem::transmute(ptr) }; //~ ERROR: dangling reference (going beyond the bounds of its allocation) +} diff --git a/src/tools/miri/tests/fail/validity/dangling_ref2.stderr b/src/tools/miri/tests/fail/validity/dangling_ref2.stderr new file mode 100644 index 0000000000000..4be4e8075a728 --- /dev/null +++ b/src/tools/miri/tests/fail/validity/dangling_ref2.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: constructing invalid value: encountered a dangling reference (going beyond the bounds of its allocation) + --> $DIR/dangling_ref2.rs:LL:CC + | +LL | let _x: &i32 = unsafe { mem::transmute(ptr) }; + | ^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered a dangling reference (going beyond the bounds of its allocation) + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/dangling_ref2.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/validity/dangling_ref3.rs b/src/tools/miri/tests/fail/validity/dangling_ref3.rs new file mode 100644 index 0000000000000..8e8a75bd7ec04 --- /dev/null +++ b/src/tools/miri/tests/fail/validity/dangling_ref3.rs @@ -0,0 +1,12 @@ +// Make sure we catch this even without Stacked Borrows +//@compile-flags: -Zmiri-disable-stacked-borrows +use std::mem; + +fn dangling() -> *const u8 { + let x = 0u8; + &x as *const _ +} + +fn main() { + let _x: &i32 = unsafe { mem::transmute(dangling()) }; //~ ERROR: dangling reference (use-after-free) +} diff --git a/src/tools/miri/tests/fail/validity/dangling_ref3.stderr b/src/tools/miri/tests/fail/validity/dangling_ref3.stderr new file mode 100644 index 0000000000000..4b7bdf7823686 --- /dev/null +++ b/src/tools/miri/tests/fail/validity/dangling_ref3.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: constructing invalid value: encountered a dangling reference (use-after-free) + --> $DIR/dangling_ref3.rs:LL:CC + | +LL | let _x: &i32 = unsafe { mem::transmute(dangling()) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered a dangling reference (use-after-free) + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/dangling_ref3.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/validity/invalid_bool.rs b/src/tools/miri/tests/fail/validity/invalid_bool.rs new file mode 100644 index 0000000000000..4f11bb2629f5f --- /dev/null +++ b/src/tools/miri/tests/fail/validity/invalid_bool.rs @@ -0,0 +1,3 @@ +fn main() { + let _b = unsafe { std::mem::transmute::(2) }; //~ ERROR: expected a boolean +} diff --git a/src/tools/miri/tests/fail/validity/invalid_bool.stderr b/src/tools/miri/tests/fail/validity/invalid_bool.stderr new file mode 100644 index 0000000000000..3972787a4d2fd --- /dev/null +++ b/src/tools/miri/tests/fail/validity/invalid_bool.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: constructing invalid value: encountered 0x02, but expected a boolean + --> $DIR/invalid_bool.rs:LL:CC + | +LL | let _b = unsafe { std::mem::transmute::(2) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered 0x02, but expected a boolean + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/invalid_bool.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/validity/invalid_bool_uninit.rs b/src/tools/miri/tests/fail/validity/invalid_bool_uninit.rs new file mode 100644 index 0000000000000..ce4fdcabd047a --- /dev/null +++ b/src/tools/miri/tests/fail/validity/invalid_bool_uninit.rs @@ -0,0 +1,10 @@ +#![allow(invalid_value)] + +union MyUninit { + init: (), + uninit: [bool; 1], +} + +fn main() { + let _b = unsafe { MyUninit { init: () }.uninit }; //~ ERROR: constructing invalid value +} diff --git a/src/tools/miri/tests/fail/validity/invalid_bool_uninit.stderr b/src/tools/miri/tests/fail/validity/invalid_bool_uninit.stderr new file mode 100644 index 0000000000000..5a7bd80e40c15 --- /dev/null +++ b/src/tools/miri/tests/fail/validity/invalid_bool_uninit.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: constructing invalid value at [0]: encountered uninitialized memory, but expected a boolean + --> $DIR/invalid_bool_uninit.rs:LL:CC + | +LL | let _b = unsafe { MyUninit { init: () }.uninit }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at [0]: encountered uninitialized memory, but expected a boolean + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/invalid_bool_uninit.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/validity/invalid_char.rs b/src/tools/miri/tests/fail/validity/invalid_char.rs new file mode 100644 index 0000000000000..568892e591096 --- /dev/null +++ b/src/tools/miri/tests/fail/validity/invalid_char.rs @@ -0,0 +1,9 @@ +fn main() { + assert!(std::char::from_u32(-1_i32 as u32).is_none()); + let _val = match unsafe { std::mem::transmute::(-1) } { + //~^ ERROR: encountered 0xffffffff, but expected a valid unicode scalar value + 'a' => true, + 'b' => false, + _ => true, + }; +} diff --git a/src/tools/miri/tests/fail/validity/invalid_char.stderr b/src/tools/miri/tests/fail/validity/invalid_char.stderr new file mode 100644 index 0000000000000..eeff289dfa4e1 --- /dev/null +++ b/src/tools/miri/tests/fail/validity/invalid_char.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: constructing invalid value: encountered $HEX, but expected a valid unicode scalar value (in `0..=$HEX` but not in `$HEX..=$HEX`) + --> $DIR/invalid_char.rs:LL:CC + | +LL | let _val = match unsafe { std::mem::transmute::(-1) } { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered $HEX, but expected a valid unicode scalar value (in `0..=$HEX` but not in `$HEX..=$HEX`) + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/invalid_char.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/validity/invalid_char_uninit.rs b/src/tools/miri/tests/fail/validity/invalid_char_uninit.rs new file mode 100644 index 0000000000000..0e3c3ccac6f23 --- /dev/null +++ b/src/tools/miri/tests/fail/validity/invalid_char_uninit.rs @@ -0,0 +1,10 @@ +#![allow(invalid_value)] + +union MyUninit { + init: (), + uninit: [char; 1], +} + +fn main() { + let _b = unsafe { MyUninit { init: () }.uninit }; //~ ERROR: constructing invalid value +} diff --git a/src/tools/miri/tests/fail/validity/invalid_char_uninit.stderr b/src/tools/miri/tests/fail/validity/invalid_char_uninit.stderr new file mode 100644 index 0000000000000..fb5d3ee1f1f9c --- /dev/null +++ b/src/tools/miri/tests/fail/validity/invalid_char_uninit.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: constructing invalid value at [0]: encountered uninitialized memory, but expected a unicode scalar value + --> $DIR/invalid_char_uninit.rs:LL:CC + | +LL | let _b = unsafe { MyUninit { init: () }.uninit }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at [0]: encountered uninitialized memory, but expected a unicode scalar value + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/invalid_char_uninit.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/validity/invalid_enum_tag.rs b/src/tools/miri/tests/fail/validity/invalid_enum_tag.rs new file mode 100644 index 0000000000000..fa115e1e78e46 --- /dev/null +++ b/src/tools/miri/tests/fail/validity/invalid_enum_tag.rs @@ -0,0 +1,11 @@ +#[repr(C)] +pub enum Foo { + A, + B, + C, + D, +} + +fn main() { + let _f = unsafe { std::mem::transmute::(42) }; //~ ERROR: constructing invalid value at .: encountered 0x0000002a, but expected a valid enum tag +} diff --git a/src/tools/miri/tests/fail/validity/invalid_enum_tag.stderr b/src/tools/miri/tests/fail/validity/invalid_enum_tag.stderr new file mode 100644 index 0000000000000..9234b4d705a9d --- /dev/null +++ b/src/tools/miri/tests/fail/validity/invalid_enum_tag.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: constructing invalid value at .: encountered $HEX, but expected a valid enum tag + --> $DIR/invalid_enum_tag.rs:LL:CC + | +LL | let _f = unsafe { std::mem::transmute::(42) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at .: encountered $HEX, but expected a valid enum tag + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/invalid_enum_tag.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/validity/invalid_fnptr_null.rs b/src/tools/miri/tests/fail/validity/invalid_fnptr_null.rs new file mode 100644 index 0000000000000..8d2045ca4a659 --- /dev/null +++ b/src/tools/miri/tests/fail/validity/invalid_fnptr_null.rs @@ -0,0 +1,5 @@ +#![allow(invalid_value)] + +fn main() { + let _b: fn() = unsafe { std::mem::transmute(0usize) }; //~ ERROR: encountered a null function pointer +} diff --git a/src/tools/miri/tests/fail/validity/invalid_fnptr_null.stderr b/src/tools/miri/tests/fail/validity/invalid_fnptr_null.stderr new file mode 100644 index 0000000000000..63fad1d56e38b --- /dev/null +++ b/src/tools/miri/tests/fail/validity/invalid_fnptr_null.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: constructing invalid value: encountered a null function pointer + --> $DIR/invalid_fnptr_null.rs:LL:CC + | +LL | let _b: fn() = unsafe { std::mem::transmute(0usize) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered a null function pointer + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/invalid_fnptr_null.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/validity/invalid_fnptr_uninit.rs b/src/tools/miri/tests/fail/validity/invalid_fnptr_uninit.rs new file mode 100644 index 0000000000000..014a8ae847a79 --- /dev/null +++ b/src/tools/miri/tests/fail/validity/invalid_fnptr_uninit.rs @@ -0,0 +1,10 @@ +#![allow(invalid_value)] + +union MyUninit { + init: (), + uninit: [fn(); 1], +} + +fn main() { + let _b = unsafe { MyUninit { init: () }.uninit }; //~ ERROR: constructing invalid value +} diff --git a/src/tools/miri/tests/fail/validity/invalid_fnptr_uninit.stderr b/src/tools/miri/tests/fail/validity/invalid_fnptr_uninit.stderr new file mode 100644 index 0000000000000..35309e90136cb --- /dev/null +++ b/src/tools/miri/tests/fail/validity/invalid_fnptr_uninit.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: constructing invalid value at [0]: encountered uninitialized memory, but expected a function pointer + --> $DIR/invalid_fnptr_uninit.rs:LL:CC + | +LL | let _b = unsafe { MyUninit { init: () }.uninit }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at [0]: encountered uninitialized memory, but expected a function pointer + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/invalid_fnptr_uninit.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/validity/invalid_wide_raw.rs b/src/tools/miri/tests/fail/validity/invalid_wide_raw.rs new file mode 100644 index 0000000000000..2ad972a9d4d7d --- /dev/null +++ b/src/tools/miri/tests/fail/validity/invalid_wide_raw.rs @@ -0,0 +1,11 @@ +#![allow(invalid_value)] + +fn main() { + trait T {} + #[derive(Debug)] + struct S { + #[allow(dead_code)] + x: *mut dyn T, + } + dbg!(S { x: unsafe { std::mem::transmute((0usize, 0usize)) } }); //~ ERROR: encountered null pointer, but expected a vtable pointer +} diff --git a/src/tools/miri/tests/fail/validity/invalid_wide_raw.stderr b/src/tools/miri/tests/fail/validity/invalid_wide_raw.stderr new file mode 100644 index 0000000000000..cf12ab8dbd55a --- /dev/null +++ b/src/tools/miri/tests/fail/validity/invalid_wide_raw.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: constructing invalid value: encountered null pointer, but expected a vtable pointer + --> $DIR/invalid_wide_raw.rs:LL:CC + | +LL | dbg!(S { x: unsafe { std::mem::transmute((0usize, 0usize)) } }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered null pointer, but expected a vtable pointer + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/invalid_wide_raw.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/validity/nonzero.rs b/src/tools/miri/tests/fail/validity/nonzero.rs new file mode 100644 index 0000000000000..384c94a556998 --- /dev/null +++ b/src/tools/miri/tests/fail/validity/nonzero.rs @@ -0,0 +1,13 @@ +// gets masked by optimizations +//@compile-flags: -Zmir-opt-level=0 +#![feature(rustc_attrs)] +#![allow(unused_attributes)] + +#[rustc_layout_scalar_valid_range_start(1)] +#[repr(transparent)] +pub(crate) struct NonZero(pub(crate) T); + +fn main() { + // Make sure that we detect this even when no function call is happening along the way + let _x = Some(unsafe { NonZero(0) }); //~ ERROR: encountered 0, but expected something greater or equal to 1 +} diff --git a/src/tools/miri/tests/fail/validity/nonzero.stderr b/src/tools/miri/tests/fail/validity/nonzero.stderr new file mode 100644 index 0000000000000..a9a68177ed973 --- /dev/null +++ b/src/tools/miri/tests/fail/validity/nonzero.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: constructing invalid value: encountered 0, but expected something greater or equal to 1 + --> $DIR/nonzero.rs:LL:CC + | +LL | let _x = Some(unsafe { NonZero(0) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered 0, but expected something greater or equal to 1 + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/nonzero.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/validity/ref_to_uninhabited1.rs b/src/tools/miri/tests/fail/validity/ref_to_uninhabited1.rs new file mode 100644 index 0000000000000..2e6be8b971c64 --- /dev/null +++ b/src/tools/miri/tests/fail/validity/ref_to_uninhabited1.rs @@ -0,0 +1,9 @@ +#![feature(never_type)] +use std::mem::{forget, transmute}; + +fn main() { + unsafe { + let x: Box = transmute(&mut 42); //~ERROR: encountered a box pointing to uninhabited type ! + forget(x); + } +} diff --git a/src/tools/miri/tests/fail/validity/ref_to_uninhabited1.stderr b/src/tools/miri/tests/fail/validity/ref_to_uninhabited1.stderr new file mode 100644 index 0000000000000..4facd2159c8d0 --- /dev/null +++ b/src/tools/miri/tests/fail/validity/ref_to_uninhabited1.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: constructing invalid value: encountered a box pointing to uninhabited type ! + --> $DIR/ref_to_uninhabited1.rs:LL:CC + | +LL | let x: Box = transmute(&mut 42); + | ^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered a box pointing to uninhabited type ! + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/ref_to_uninhabited1.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/validity/ref_to_uninhabited2.rs b/src/tools/miri/tests/fail/validity/ref_to_uninhabited2.rs new file mode 100644 index 0000000000000..8934a06b5d73a --- /dev/null +++ b/src/tools/miri/tests/fail/validity/ref_to_uninhabited2.rs @@ -0,0 +1,9 @@ +use std::mem::transmute; + +enum Void {} + +fn main() { + unsafe { + let _x: &(i32, Void) = transmute(&42); //~ERROR: encountered a reference pointing to uninhabited type (i32, Void) + } +} diff --git a/src/tools/miri/tests/fail/validity/ref_to_uninhabited2.stderr b/src/tools/miri/tests/fail/validity/ref_to_uninhabited2.stderr new file mode 100644 index 0000000000000..264465f939190 --- /dev/null +++ b/src/tools/miri/tests/fail/validity/ref_to_uninhabited2.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: constructing invalid value: encountered a reference pointing to uninhabited type (i32, Void) + --> $DIR/ref_to_uninhabited2.rs:LL:CC + | +LL | let _x: &(i32, Void) = transmute(&42); + | ^^^^^^^^^^^^^^ constructing invalid value: encountered a reference pointing to uninhabited type (i32, Void) + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/ref_to_uninhabited2.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/validity/too-big-slice.rs b/src/tools/miri/tests/fail/validity/too-big-slice.rs new file mode 100644 index 0000000000000..61d9032207585 --- /dev/null +++ b/src/tools/miri/tests/fail/validity/too-big-slice.rs @@ -0,0 +1,8 @@ +use std::mem; + +fn main() { + unsafe { + let ptr = Box::into_raw(Box::new(0u8)); + let _x: &[u8] = mem::transmute((ptr, usize::MAX)); //~ ERROR: invalid reference metadata: slice is bigger than largest supported object + } +} diff --git a/src/tools/miri/tests/fail/validity/too-big-slice.stderr b/src/tools/miri/tests/fail/validity/too-big-slice.stderr new file mode 100644 index 0000000000000..6df00aefe7561 --- /dev/null +++ b/src/tools/miri/tests/fail/validity/too-big-slice.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: constructing invalid value: encountered invalid reference metadata: slice is bigger than largest supported object + --> $DIR/too-big-slice.rs:LL:CC + | +LL | let _x: &[u8] = mem::transmute((ptr, usize::MAX)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered invalid reference metadata: slice is bigger than largest supported object + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/too-big-slice.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/validity/too-big-unsized.rs b/src/tools/miri/tests/fail/validity/too-big-unsized.rs new file mode 100644 index 0000000000000..280205dccbf23 --- /dev/null +++ b/src/tools/miri/tests/fail/validity/too-big-unsized.rs @@ -0,0 +1,15 @@ +use std::mem; + +#[allow(unused)] +struct MySlice { + prefix: u64, + tail: [u8], +} + +fn main() { + unsafe { + let ptr = Box::into_raw(Box::new(0u8)); + // The slice part is actually not "too big", but together with the `prefix` field it is. + let _x: &MySlice = mem::transmute((ptr, isize::MAX as usize)); //~ ERROR: invalid reference metadata: total size is bigger than largest supported object + } +} diff --git a/src/tools/miri/tests/fail/validity/too-big-unsized.stderr b/src/tools/miri/tests/fail/validity/too-big-unsized.stderr new file mode 100644 index 0000000000000..cbcb31b29fc30 --- /dev/null +++ b/src/tools/miri/tests/fail/validity/too-big-unsized.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: constructing invalid value: encountered invalid reference metadata: total size is bigger than largest supported object + --> $DIR/too-big-unsized.rs:LL:CC + | +LL | let _x: &MySlice = mem::transmute((ptr, isize::MAX as usize)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered invalid reference metadata: total size is bigger than largest supported object + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/too-big-unsized.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/validity/transmute_through_ptr.rs b/src/tools/miri/tests/fail/validity/transmute_through_ptr.rs new file mode 100644 index 0000000000000..60b3bdd6cd69a --- /dev/null +++ b/src/tools/miri/tests/fail/validity/transmute_through_ptr.rs @@ -0,0 +1,19 @@ +#[repr(u32)] +#[derive(Debug)] +enum Bool { + True, +} + +fn evil(x: &mut Bool) { + let x = x as *mut _ as *mut u32; + unsafe { *x = 44 }; // out-of-bounds enum tag +} + +#[rustfmt::skip] // rustfmt bug: https://github.com/rust-lang/rustfmt/issues/5391 +fn main() { + let mut x = Bool::True; + evil(&mut x); + let y = x; // reading this ought to be enough to trigger validation + //~^ ERROR: constructing invalid value at .: encountered 0x0000002c, but expected a valid enum tag + println!("{:?}", y); // make sure it is used (and not optimized away) +} diff --git a/src/tools/miri/tests/fail/validity/transmute_through_ptr.stderr b/src/tools/miri/tests/fail/validity/transmute_through_ptr.stderr new file mode 100644 index 0000000000000..ea155405cd610 --- /dev/null +++ b/src/tools/miri/tests/fail/validity/transmute_through_ptr.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: constructing invalid value at .: encountered $HEX, but expected a valid enum tag + --> $DIR/transmute_through_ptr.rs:LL:CC + | +LL | let y = x; // reading this ought to be enough to trigger validation + | ^ constructing invalid value at .: encountered $HEX, but expected a valid enum tag + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/transmute_through_ptr.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/validity/uninit_float.rs b/src/tools/miri/tests/fail/validity/uninit_float.rs new file mode 100644 index 0000000000000..045bb46464fae --- /dev/null +++ b/src/tools/miri/tests/fail/validity/uninit_float.rs @@ -0,0 +1,9 @@ +#![allow(deprecated, invalid_value)] +// This test is adapted from https://github.com/rust-lang/miri/issues/1340#issue-600900312. + +fn main() { + // Deliberately using `mem::uninitialized` to make sure that despite all the mitigations, we consider this UB. + // The array avoids a `Scalar` layout which detects uninit without even doing validation. + let _val: [f32; 1] = unsafe { std::mem::uninitialized() }; + //~^ ERROR: uninitialized +} diff --git a/src/tools/miri/tests/fail/validity/uninit_float.stderr b/src/tools/miri/tests/fail/validity/uninit_float.stderr new file mode 100644 index 0000000000000..677a0fc5570d7 --- /dev/null +++ b/src/tools/miri/tests/fail/validity/uninit_float.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: constructing invalid value at .value[0]: encountered uninitialized bytes + --> $DIR/uninit_float.rs:LL:CC + | +LL | let _val: [f32; 1] = unsafe { std::mem::uninitialized() }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at .value[0]: encountered uninitialized bytes + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/uninit_float.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/validity/uninit_integer.rs b/src/tools/miri/tests/fail/validity/uninit_integer.rs new file mode 100644 index 0000000000000..a94302603a219 --- /dev/null +++ b/src/tools/miri/tests/fail/validity/uninit_integer.rs @@ -0,0 +1,8 @@ +#![allow(invalid_value)] +// This test is from https://github.com/rust-lang/miri/issues/1340#issue-600900312. + +fn main() { + // The array avoids a `Scalar` layout which detects uninit without even doing validation. + let _val = unsafe { std::mem::MaybeUninit::<[usize; 1]>::uninit().assume_init() }; + //~^ ERROR: uninitialized +} diff --git a/src/tools/miri/tests/fail/validity/uninit_integer.stderr b/src/tools/miri/tests/fail/validity/uninit_integer.stderr new file mode 100644 index 0000000000000..a9ac2a6dc67e7 --- /dev/null +++ b/src/tools/miri/tests/fail/validity/uninit_integer.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: constructing invalid value at .value[0]: encountered uninitialized bytes + --> $DIR/uninit_integer.rs:LL:CC + | +LL | let _val = unsafe { std::mem::MaybeUninit::<[usize; 1]>::uninit().assume_init() }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at .value[0]: encountered uninitialized bytes + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/uninit_integer.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/validity/uninit_raw_ptr.rs b/src/tools/miri/tests/fail/validity/uninit_raw_ptr.rs new file mode 100644 index 0000000000000..18703152ea108 --- /dev/null +++ b/src/tools/miri/tests/fail/validity/uninit_raw_ptr.rs @@ -0,0 +1,7 @@ +#![allow(invalid_value)] + +fn main() { + // The array avoids a `Scalar` layout which detects uninit without even doing validation. + let _val = unsafe { std::mem::MaybeUninit::<[*const u8; 1]>::uninit().assume_init() }; + //~^ ERROR: uninitialized +} diff --git a/src/tools/miri/tests/fail/validity/uninit_raw_ptr.stderr b/src/tools/miri/tests/fail/validity/uninit_raw_ptr.stderr new file mode 100644 index 0000000000000..bbae9cf69ffe1 --- /dev/null +++ b/src/tools/miri/tests/fail/validity/uninit_raw_ptr.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: constructing invalid value at .value[0]: encountered uninitialized memory, but expected a raw pointer + --> $DIR/uninit_raw_ptr.rs:LL:CC + | +LL | let _val = unsafe { std::mem::MaybeUninit::<[*const u8; 1]>::uninit().assume_init() }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at .value[0]: encountered uninitialized memory, but expected a raw pointer + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/uninit_raw_ptr.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/weak_memory/racing_mixed_size.rs b/src/tools/miri/tests/fail/weak_memory/racing_mixed_size.rs new file mode 100644 index 0000000000000..7bbb7f9fe7c2a --- /dev/null +++ b/src/tools/miri/tests/fail/weak_memory/racing_mixed_size.rs @@ -0,0 +1,40 @@ +// We want to control preemption here. +//@compile-flags: -Zmiri-preemption-rate=0 + +#![feature(core_intrinsics)] + +use std::ptr; +use std::sync::atomic::AtomicU32; +use std::sync::atomic::Ordering::*; +use std::thread::spawn; + +fn static_atomic_u32(val: u32) -> &'static AtomicU32 { + let ret = Box::leak(Box::new(AtomicU32::new(val))); + ret +} + +fn split_u32_ptr(dword: *const u32) -> *const [u16; 2] { + unsafe { std::mem::transmute::<*const u32, *const [u16; 2]>(dword) } +} + +// Wine's SRWLock implementation does this, which is definitely undefined in C++ memory model +// https://github.com/wine-mirror/wine/blob/303f8042f9db508adaca02ef21f8de4992cb9c03/dlls/ntdll/sync.c#L543-L566 +// Though it probably works just fine on x86 +pub fn main() { + let x = static_atomic_u32(0); + let j1 = spawn(move || { + x.store(1, Relaxed); + }); + + let j2 = spawn(move || { + let x_ptr = x as *const AtomicU32 as *const u32; + let x_split = split_u32_ptr(x_ptr); + unsafe { + let hi = ptr::addr_of!((*x_split)[0]); + std::intrinsics::atomic_load_relaxed(hi); //~ ERROR: imperfectly overlapping + } + }); + + j1.join().unwrap(); + j2.join().unwrap(); +} diff --git a/src/tools/miri/tests/fail/weak_memory/racing_mixed_size.stderr b/src/tools/miri/tests/fail/weak_memory/racing_mixed_size.stderr new file mode 100644 index 0000000000000..dda22ac9ce24c --- /dev/null +++ b/src/tools/miri/tests/fail/weak_memory/racing_mixed_size.stderr @@ -0,0 +1,14 @@ +error: unsupported operation: racy imperfectly overlapping atomic access is not possible in the C++20 memory model, and not supported by Miri's weak memory emulation + --> $DIR/racing_mixed_size.rs:LL:CC + | +LL | std::intrinsics::atomic_load_relaxed(hi); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ racy imperfectly overlapping atomic access is not possible in the C++20 memory model, and not supported by Miri's weak memory emulation + | + = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support + = note: BACKTRACE: + = note: inside closure at $DIR/racing_mixed_size.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/weak_memory/racing_mixed_size_read.rs b/src/tools/miri/tests/fail/weak_memory/racing_mixed_size_read.rs new file mode 100644 index 0000000000000..73178980b7e5a --- /dev/null +++ b/src/tools/miri/tests/fail/weak_memory/racing_mixed_size_read.rs @@ -0,0 +1,38 @@ +// We want to control preemption here. +//@compile-flags: -Zmiri-preemption-rate=0 + +use std::sync::atomic::Ordering::*; +use std::sync::atomic::{AtomicU16, AtomicU32}; +use std::thread::spawn; + +fn static_atomic(val: u32) -> &'static AtomicU32 { + let ret = Box::leak(Box::new(AtomicU32::new(val))); + ret +} + +fn split_u32_ptr(dword: *const u32) -> *const [u16; 2] { + unsafe { std::mem::transmute::<*const u32, *const [u16; 2]>(dword) } +} + +// Racing mixed size reads may cause two loads to read-from +// the same store but observe different values, which doesn't make +// sense under the formal model so we forbade this. +pub fn main() { + let x = static_atomic(0); + + let j1 = spawn(move || { + x.load(Relaxed); + }); + + let j2 = spawn(move || { + let x_ptr = x as *const AtomicU32 as *const u32; + let x_split = split_u32_ptr(x_ptr); + unsafe { + let hi = x_split as *const u16 as *const AtomicU16; + (*hi).load(Relaxed); //~ ERROR: imperfectly overlapping + } + }); + + j1.join().unwrap(); + j2.join().unwrap(); +} diff --git a/src/tools/miri/tests/fail/weak_memory/racing_mixed_size_read.stderr b/src/tools/miri/tests/fail/weak_memory/racing_mixed_size_read.stderr new file mode 100644 index 0000000000000..59fa5c7410237 --- /dev/null +++ b/src/tools/miri/tests/fail/weak_memory/racing_mixed_size_read.stderr @@ -0,0 +1,14 @@ +error: unsupported operation: racy imperfectly overlapping atomic access is not possible in the C++20 memory model, and not supported by Miri's weak memory emulation + --> $DIR/racing_mixed_size_read.rs:LL:CC + | +LL | (*hi).load(Relaxed); + | ^^^^^^^^^^^^^^^^^^^ racy imperfectly overlapping atomic access is not possible in the C++20 memory model, and not supported by Miri's weak memory emulation + | + = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support + = note: BACKTRACE: + = note: inside closure at $DIR/racing_mixed_size_read.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/zst1.rs b/src/tools/miri/tests/fail/zst1.rs new file mode 100644 index 0000000000000..cc81481e4fa4c --- /dev/null +++ b/src/tools/miri/tests/fail/zst1.rs @@ -0,0 +1,5 @@ +fn main() { + // make sure ZST locals cannot be accessed + let x = &() as *const () as *const i8; + let _val = unsafe { *x }; //~ ERROR: out-of-bounds +} diff --git a/src/tools/miri/tests/fail/zst1.stderr b/src/tools/miri/tests/fail/zst1.stderr new file mode 100644 index 0000000000000..b89f06af95893 --- /dev/null +++ b/src/tools/miri/tests/fail/zst1.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: dereferencing pointer failed: ALLOC has size 0, so pointer to 1 byte starting at offset 0 is out-of-bounds + --> $DIR/zst1.rs:LL:CC + | +LL | let _val = unsafe { *x }; + | ^^ dereferencing pointer failed: ALLOC has size 0, so pointer to 1 byte starting at offset 0 is out-of-bounds + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/zst1.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/zst2.rs b/src/tools/miri/tests/fail/zst2.rs new file mode 100644 index 0000000000000..82470866f179f --- /dev/null +++ b/src/tools/miri/tests/fail/zst2.rs @@ -0,0 +1,15 @@ +// Some optimizations remove ZST accesses, thus masking this UB. +//@compile-flags: -Zmir-opt-level=0 + +fn main() { + // Not using the () type here, as writes of that type do not even have MIR generated. + // Also not assigning directly as that's array initialization, not assignment. + let zst_val = [1u8; 0]; + + // make sure ZST accesses are checked against being "truly" dangling pointers + // (into deallocated allocations). + let mut x_box = Box::new(1u8); + let x = &mut *x_box as *mut _ as *mut [u8; 0]; + drop(x_box); + unsafe { *x = zst_val }; //~ ERROR: dereferenced after this allocation got freed +} diff --git a/src/tools/miri/tests/fail/zst2.stderr b/src/tools/miri/tests/fail/zst2.stderr new file mode 100644 index 0000000000000..6c49656e4c67d --- /dev/null +++ b/src/tools/miri/tests/fail/zst2.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed + --> $DIR/zst2.rs:LL:CC + | +LL | unsafe { *x = zst_val }; + | ^^^^^^^^^^^^ pointer to ALLOC was dereferenced after this allocation got freed + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/zst2.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/zst3.rs b/src/tools/miri/tests/fail/zst3.rs new file mode 100644 index 0000000000000..a511f38998feb --- /dev/null +++ b/src/tools/miri/tests/fail/zst3.rs @@ -0,0 +1,18 @@ +// Some optimizations remove ZST accesses, thus masking this UB. +//@compile-flags: -Zmir-opt-level=0 + +fn main() { + // Not using the () type here, as writes of that type do not even have MIR generated. + // Also not assigning directly as that's array initialization, not assignment. + let zst_val = [1u8; 0]; + + // make sure ZST accesses are checked against being "truly" dangling pointers + // (that are out-of-bounds). + let mut x_box = Box::new(1u8); + let x = (&mut *x_box as *mut u8).wrapping_offset(1); + // This one is just "at the edge", but still okay + unsafe { *(x as *mut [u8; 0]) = zst_val }; + // One byte further is OOB. + let x = x.wrapping_offset(1); + unsafe { *(x as *mut [u8; 0]) = zst_val }; //~ ERROR: out-of-bounds +} diff --git a/src/tools/miri/tests/fail/zst3.stderr b/src/tools/miri/tests/fail/zst3.stderr new file mode 100644 index 0000000000000..c9accf2c8fbeb --- /dev/null +++ b/src/tools/miri/tests/fail/zst3.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: dereferencing pointer failed: ALLOC has size 1, so pointer at offset 2 is out-of-bounds + --> $DIR/zst3.rs:LL:CC + | +LL | unsafe { *(x as *mut [u8; 0]) = zst_val }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ dereferencing pointer failed: ALLOC has size 1, so pointer at offset 2 is out-of-bounds + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/zst3.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/panic/div-by-zero-2.rs b/src/tools/miri/tests/panic/div-by-zero-2.rs new file mode 100644 index 0000000000000..fac5415696fc6 --- /dev/null +++ b/src/tools/miri/tests/panic/div-by-zero-2.rs @@ -0,0 +1,5 @@ +#![allow(unconditional_panic)] + +fn main() { + let _n = 1 / 0; +} diff --git a/src/tools/miri/tests/panic/div-by-zero-2.stderr b/src/tools/miri/tests/panic/div-by-zero-2.stderr new file mode 100644 index 0000000000000..538d87113654d --- /dev/null +++ b/src/tools/miri/tests/panic/div-by-zero-2.stderr @@ -0,0 +1,2 @@ +thread 'main' panicked at 'attempt to divide by zero', $DIR/div-by-zero-2.rs:LL:CC +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/src/tools/miri/tests/panic/function_calls/exported_symbol_good_unwind.rs b/src/tools/miri/tests/panic/function_calls/exported_symbol_good_unwind.rs new file mode 100644 index 0000000000000..71b799a1f12ba --- /dev/null +++ b/src/tools/miri/tests/panic/function_calls/exported_symbol_good_unwind.rs @@ -0,0 +1,38 @@ +// Make sure the workaround for "crate ... required to be available in rlib format, but was not +// found in this form" errors works without `-C prefer-dynamic` (`panic!` calls foreign function +// `__rust_start_panic`). +// no-prefer-dynamic +#![feature(c_unwind, unboxed_closures)] + +use std::panic; + +#[no_mangle] +extern "C-unwind" fn good_unwind_c() { + panic!(); +} + +#[no_mangle] +fn good_unwind_rust() { + panic!(); +} + +// Diverging function calls are on a different code path. +#[no_mangle] +extern "rust-call" fn good_unwind_rust_call(_: ()) -> ! { + panic!(); +} + +fn main() -> ! { + extern "C-unwind" { + fn good_unwind_c(); + } + panic::catch_unwind(|| unsafe { good_unwind_c() }).unwrap_err(); + extern "Rust" { + fn good_unwind_rust(); + } + panic::catch_unwind(|| unsafe { good_unwind_rust() }).unwrap_err(); + extern "rust-call" { + fn good_unwind_rust_call(_: ()) -> !; + } + unsafe { good_unwind_rust_call(()) } +} diff --git a/src/tools/miri/tests/panic/function_calls/exported_symbol_good_unwind.stderr b/src/tools/miri/tests/panic/function_calls/exported_symbol_good_unwind.stderr new file mode 100644 index 0000000000000..bff897775e8f6 --- /dev/null +++ b/src/tools/miri/tests/panic/function_calls/exported_symbol_good_unwind.stderr @@ -0,0 +1,4 @@ +thread 'main' panicked at 'explicit panic', $DIR/exported_symbol_good_unwind.rs:LL:CC +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +thread 'main' panicked at 'explicit panic', $DIR/exported_symbol_good_unwind.rs:LL:CC +thread 'main' panicked at 'explicit panic', $DIR/exported_symbol_good_unwind.rs:LL:CC diff --git a/src/tools/miri/tests/panic/overflowing-lsh-neg.rs b/src/tools/miri/tests/panic/overflowing-lsh-neg.rs new file mode 100644 index 0000000000000..bf5eed1c550f1 --- /dev/null +++ b/src/tools/miri/tests/panic/overflowing-lsh-neg.rs @@ -0,0 +1,5 @@ +#![allow(arithmetic_overflow)] + +fn main() { + let _n = 2i64 << -1; +} diff --git a/src/tools/miri/tests/panic/overflowing-lsh-neg.stderr b/src/tools/miri/tests/panic/overflowing-lsh-neg.stderr new file mode 100644 index 0000000000000..21e434d873f7b --- /dev/null +++ b/src/tools/miri/tests/panic/overflowing-lsh-neg.stderr @@ -0,0 +1,2 @@ +thread 'main' panicked at 'attempt to shift left with overflow', $DIR/overflowing-lsh-neg.rs:LL:CC +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/src/tools/miri/tests/panic/overflowing-rsh-1.rs b/src/tools/miri/tests/panic/overflowing-rsh-1.rs new file mode 100644 index 0000000000000..4c0106f0fb1fd --- /dev/null +++ b/src/tools/miri/tests/panic/overflowing-rsh-1.rs @@ -0,0 +1,5 @@ +#![allow(arithmetic_overflow)] + +fn main() { + let _n = 1i64 >> 64; +} diff --git a/src/tools/miri/tests/panic/overflowing-rsh-1.stderr b/src/tools/miri/tests/panic/overflowing-rsh-1.stderr new file mode 100644 index 0000000000000..fd04bf1bd4ec3 --- /dev/null +++ b/src/tools/miri/tests/panic/overflowing-rsh-1.stderr @@ -0,0 +1,2 @@ +thread 'main' panicked at 'attempt to shift right with overflow', $DIR/overflowing-rsh-1.rs:LL:CC +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/src/tools/miri/tests/panic/overflowing-rsh-2.rs b/src/tools/miri/tests/panic/overflowing-rsh-2.rs new file mode 100644 index 0000000000000..19d16e7bc84a2 --- /dev/null +++ b/src/tools/miri/tests/panic/overflowing-rsh-2.rs @@ -0,0 +1,6 @@ +#![allow(arithmetic_overflow)] + +fn main() { + // Make sure we catch overflows that would be hidden by first casting the RHS to u32 + let _n = 1i64 >> (u32::MAX as i64 + 1); +} diff --git a/src/tools/miri/tests/panic/overflowing-rsh-2.stderr b/src/tools/miri/tests/panic/overflowing-rsh-2.stderr new file mode 100644 index 0000000000000..eb568e4d742bc --- /dev/null +++ b/src/tools/miri/tests/panic/overflowing-rsh-2.stderr @@ -0,0 +1,2 @@ +thread 'main' panicked at 'attempt to shift right with overflow', $DIR/overflowing-rsh-2.rs:LL:CC +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/src/tools/miri/tests/panic/panic1.rs b/src/tools/miri/tests/panic/panic1.rs new file mode 100644 index 0000000000000..dbddf41fdbf71 --- /dev/null +++ b/src/tools/miri/tests/panic/panic1.rs @@ -0,0 +1,6 @@ +//@rustc-env: RUST_BACKTRACE=1 +//@compile-flags: -Zmiri-disable-isolation + +fn main() { + std::panic!("panicking from libstd"); +} diff --git a/src/tools/miri/tests/panic/panic1.stderr b/src/tools/miri/tests/panic/panic1.stderr new file mode 100644 index 0000000000000..15834d58bc6e5 --- /dev/null +++ b/src/tools/miri/tests/panic/panic1.stderr @@ -0,0 +1,9 @@ +thread 'main' panicked at 'panicking from libstd', $DIR/panic1.rs:LL:CC +stack backtrace: + 0: std::rt::begin_panic + at RUSTLIB/std/src/panicking.rs:LL:CC + 1: main + at $DIR/panic1.rs:LL:CC + 2: >::call_once - shim(fn()) + at RUSTLIB/core/src/ops/function.rs:LL:CC +note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace. diff --git a/src/tools/miri/tests/panic/panic2.rs b/src/tools/miri/tests/panic/panic2.rs new file mode 100644 index 0000000000000..d90e3f2e0ac13 --- /dev/null +++ b/src/tools/miri/tests/panic/panic2.rs @@ -0,0 +1,3 @@ +fn main() { + std::panic!("{}-panicking from libstd", 42); +} diff --git a/src/tools/miri/tests/panic/panic2.stderr b/src/tools/miri/tests/panic/panic2.stderr new file mode 100644 index 0000000000000..c192ca3f64c36 --- /dev/null +++ b/src/tools/miri/tests/panic/panic2.stderr @@ -0,0 +1,2 @@ +thread 'main' panicked at '42-panicking from libstd', $DIR/panic2.rs:LL:CC +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/src/tools/miri/tests/panic/panic3.rs b/src/tools/miri/tests/panic/panic3.rs new file mode 100644 index 0000000000000..418ee4f8411eb --- /dev/null +++ b/src/tools/miri/tests/panic/panic3.rs @@ -0,0 +1,3 @@ +fn main() { + core::panic!("panicking from libcore"); +} diff --git a/src/tools/miri/tests/panic/panic3.stderr b/src/tools/miri/tests/panic/panic3.stderr new file mode 100644 index 0000000000000..0ce4a37fd5197 --- /dev/null +++ b/src/tools/miri/tests/panic/panic3.stderr @@ -0,0 +1,2 @@ +thread 'main' panicked at 'panicking from libcore', $DIR/panic3.rs:LL:CC +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/src/tools/miri/tests/panic/panic4.rs b/src/tools/miri/tests/panic/panic4.rs new file mode 100644 index 0000000000000..0fcc53813b5d7 --- /dev/null +++ b/src/tools/miri/tests/panic/panic4.rs @@ -0,0 +1,3 @@ +fn main() { + core::panic!("{}-panicking from libcore", 42); +} diff --git a/src/tools/miri/tests/panic/panic4.stderr b/src/tools/miri/tests/panic/panic4.stderr new file mode 100644 index 0000000000000..82df953b61c03 --- /dev/null +++ b/src/tools/miri/tests/panic/panic4.stderr @@ -0,0 +1,2 @@ +thread 'main' panicked at '42-panicking from libcore', $DIR/panic4.rs:LL:CC +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/src/tools/miri/tests/panic/transmute_fat2.rs b/src/tools/miri/tests/panic/transmute_fat2.rs new file mode 100644 index 0000000000000..0205433ad9fb9 --- /dev/null +++ b/src/tools/miri/tests/panic/transmute_fat2.rs @@ -0,0 +1,10 @@ +fn main() { + #[cfg(all(target_endian = "little", target_pointer_width = "64"))] + let bad = unsafe { std::mem::transmute::(42) }; + #[cfg(all(target_endian = "big", target_pointer_width = "64"))] + let bad = unsafe { std::mem::transmute::(42 << 64) }; + #[cfg(all(target_endian = "little", target_pointer_width = "32"))] + let bad = unsafe { std::mem::transmute::(42) }; + // This created a slice with length 0, so the following will fail the bounds check. + bad[0]; +} diff --git a/src/tools/miri/tests/panic/transmute_fat2.stderr b/src/tools/miri/tests/panic/transmute_fat2.stderr new file mode 100644 index 0000000000000..f497ab672550f --- /dev/null +++ b/src/tools/miri/tests/panic/transmute_fat2.stderr @@ -0,0 +1,2 @@ +thread 'main' panicked at 'index out of bounds: the len is 0 but the index is 0', $DIR/transmute_fat2.rs:LL:CC +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/src/tools/miri/tests/panic/unsupported_foreign_function.rs b/src/tools/miri/tests/panic/unsupported_foreign_function.rs new file mode 100644 index 0000000000000..a78646528fb12 --- /dev/null +++ b/src/tools/miri/tests/panic/unsupported_foreign_function.rs @@ -0,0 +1,11 @@ +//@compile-flags: -Zmiri-panic-on-unsupported + +fn main() { + extern "Rust" { + fn foo(); + } + + unsafe { + foo(); + } +} diff --git a/src/tools/miri/tests/panic/unsupported_foreign_function.stderr b/src/tools/miri/tests/panic/unsupported_foreign_function.stderr new file mode 100644 index 0000000000000..9af3e48655f38 --- /dev/null +++ b/src/tools/miri/tests/panic/unsupported_foreign_function.stderr @@ -0,0 +1,2 @@ +thread 'main' panicked at 'unsupported Miri functionality: can't call foreign function: foo', $DIR/unsupported_foreign_function.rs:LL:CC +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/src/tools/miri/tests/panic/unsupported_syscall.rs b/src/tools/miri/tests/panic/unsupported_syscall.rs new file mode 100644 index 0000000000000..31d666e1d9d80 --- /dev/null +++ b/src/tools/miri/tests/panic/unsupported_syscall.rs @@ -0,0 +1,9 @@ +//@ignore-target-windows: No libc on Windows +//@ignore-target-apple: `syscall` is not supported on macOS +//@compile-flags: -Zmiri-panic-on-unsupported + +fn main() { + unsafe { + libc::syscall(0); + } +} diff --git a/src/tools/miri/tests/panic/unsupported_syscall.stderr b/src/tools/miri/tests/panic/unsupported_syscall.stderr new file mode 100644 index 0000000000000..90aa5a9073638 --- /dev/null +++ b/src/tools/miri/tests/panic/unsupported_syscall.stderr @@ -0,0 +1,2 @@ +thread 'main' panicked at 'unsupported Miri functionality: can't execute syscall with ID 0', $DIR/unsupported_syscall.rs:LL:CC +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/src/tools/miri/tests/pass-dep/calloc.rs b/src/tools/miri/tests/pass-dep/calloc.rs new file mode 100644 index 0000000000000..62ab63c5fc788 --- /dev/null +++ b/src/tools/miri/tests/pass-dep/calloc.rs @@ -0,0 +1,22 @@ +//@ignore-target-windows: No libc on Windows + +use core::slice; + +fn main() { + unsafe { + let p1 = libc::calloc(0, 0); + assert!(p1.is_null()); + + let p2 = libc::calloc(20, 0); + assert!(p2.is_null()); + + let p3 = libc::calloc(0, 20); + assert!(p3.is_null()); + + let p4 = libc::calloc(4, 8); + assert!(!p4.is_null()); + let slice = slice::from_raw_parts(p4 as *const u8, 4 * 8); + assert_eq!(&slice, &[0_u8; 4 * 8]); + libc::free(p4); + } +} diff --git a/src/tools/miri/tests/pass-dep/concurrency/libc_pthread_cond.rs b/src/tools/miri/tests/pass-dep/concurrency/libc_pthread_cond.rs new file mode 100644 index 0000000000000..b0325f7d78e50 --- /dev/null +++ b/src/tools/miri/tests/pass-dep/concurrency/libc_pthread_cond.rs @@ -0,0 +1,84 @@ +//@ignore-target-windows: No libc on Windows +//@ignore-target-apple: pthread_condattr_setclock is not supported on MacOS. +//@compile-flags: -Zmiri-disable-isolation + +/// Test that conditional variable timeouts are working properly with both +/// monotonic and system clocks. +use std::mem::MaybeUninit; +use std::time::Instant; + +fn test_timed_wait_timeout(clock_id: i32) { + unsafe { + let mut attr: MaybeUninit = MaybeUninit::uninit(); + assert_eq!(libc::pthread_condattr_init(attr.as_mut_ptr()), 0); + assert_eq!(libc::pthread_condattr_setclock(attr.as_mut_ptr(), clock_id), 0); + + let mut cond: MaybeUninit = MaybeUninit::uninit(); + assert_eq!(libc::pthread_cond_init(cond.as_mut_ptr(), attr.as_ptr()), 0); + assert_eq!(libc::pthread_condattr_destroy(attr.as_mut_ptr()), 0); + + let mut mutex: libc::pthread_mutex_t = libc::PTHREAD_MUTEX_INITIALIZER; + + let mut now_mu: MaybeUninit = MaybeUninit::uninit(); + assert_eq!(libc::clock_gettime(clock_id, now_mu.as_mut_ptr()), 0); + let now = now_mu.assume_init(); + // Waiting for a second... mostly because waiting less requires mich more tricky arithmetic. + // FIXME: wait less. + let timeout = libc::timespec { tv_sec: now.tv_sec + 1, tv_nsec: now.tv_nsec }; + + assert_eq!(libc::pthread_mutex_lock(&mut mutex as *mut _), 0); + let current_time = Instant::now(); + assert_eq!( + libc::pthread_cond_timedwait(cond.as_mut_ptr(), &mut mutex as *mut _, &timeout), + libc::ETIMEDOUT + ); + let elapsed_time = current_time.elapsed().as_millis(); + assert!(900 <= elapsed_time && elapsed_time <= 1300); + + // Test calling `pthread_cond_timedwait` again with an already elapsed timeout. + assert_eq!( + libc::pthread_cond_timedwait(cond.as_mut_ptr(), &mut mutex as *mut _, &timeout), + libc::ETIMEDOUT + ); + + // Test that invalid nanosecond values (above 10^9 or negative) are rejected with the + // correct error code. + let invalid_timeout_1 = libc::timespec { tv_sec: now.tv_sec + 1, tv_nsec: 1_000_000_000 }; + assert_eq!( + libc::pthread_cond_timedwait( + cond.as_mut_ptr(), + &mut mutex as *mut _, + &invalid_timeout_1 + ), + libc::EINVAL + ); + let invalid_timeout_2 = libc::timespec { tv_sec: now.tv_sec + 1, tv_nsec: -1 }; + assert_eq!( + libc::pthread_cond_timedwait( + cond.as_mut_ptr(), + &mut mutex as *mut _, + &invalid_timeout_2 + ), + libc::EINVAL + ); + // Test that invalid second values (negative) are rejected with the correct error code. + let invalid_timeout_3 = libc::timespec { tv_sec: -1, tv_nsec: 0 }; + assert_eq!( + libc::pthread_cond_timedwait( + cond.as_mut_ptr(), + &mut mutex as *mut _, + &invalid_timeout_3 + ), + libc::EINVAL + ); + + assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), 0); + assert_eq!(libc::pthread_mutex_destroy(&mut mutex as *mut _), 0); + assert_eq!(libc::pthread_cond_destroy(cond.as_mut_ptr()), 0); + } +} + +fn main() { + test_timed_wait_timeout(libc::CLOCK_MONOTONIC); + test_timed_wait_timeout(libc::CLOCK_REALTIME); +} diff --git a/src/tools/miri/tests/pass-dep/concurrency/linux-futex.rs b/src/tools/miri/tests/pass-dep/concurrency/linux-futex.rs new file mode 100644 index 0000000000000..a456528ec2074 --- /dev/null +++ b/src/tools/miri/tests/pass-dep/concurrency/linux-futex.rs @@ -0,0 +1,291 @@ +//@only-target-linux +//@compile-flags: -Zmiri-disable-isolation + +use std::mem::MaybeUninit; +use std::ptr; +use std::sync::atomic::AtomicI32; +use std::sync::atomic::Ordering; +use std::thread; +use std::time::{Duration, Instant}; + +fn wake_nobody() { + let futex = 0; + + // Wake 1 waiter. Expect zero waiters woken up, as nobody is waiting. + unsafe { + assert_eq!(libc::syscall(libc::SYS_futex, &futex as *const i32, libc::FUTEX_WAKE, 1), 0); + } + + // Same, but without omitting the unused arguments. + unsafe { + assert_eq!( + libc::syscall( + libc::SYS_futex, + &futex as *const i32, + libc::FUTEX_WAKE, + 1, + ptr::null::(), + 0usize, + 0, + ), + 0, + ); + } +} + +fn wake_dangling() { + let futex = Box::new(0); + let ptr: *const i32 = &*futex; + drop(futex); + + // Wake 1 waiter. Expect zero waiters woken up, as nobody is waiting. + unsafe { + assert_eq!(libc::syscall(libc::SYS_futex, ptr, libc::FUTEX_WAKE, 1), 0); + } +} + +fn wait_wrong_val() { + let futex: i32 = 123; + + // Only wait if the futex value is 456. + unsafe { + assert_eq!( + libc::syscall( + libc::SYS_futex, + &futex as *const i32, + libc::FUTEX_WAIT, + 456, + ptr::null::(), + ), + -1, + ); + assert_eq!(*libc::__errno_location(), libc::EAGAIN); + } +} + +fn wait_timeout() { + let start = Instant::now(); + + let futex: i32 = 123; + + // Wait for 200ms, with nobody waking us up early. + unsafe { + assert_eq!( + libc::syscall( + libc::SYS_futex, + &futex as *const i32, + libc::FUTEX_WAIT, + 123, + &libc::timespec { tv_sec: 0, tv_nsec: 200_000_000 }, + ), + -1, + ); + assert_eq!(*libc::__errno_location(), libc::ETIMEDOUT); + } + + assert!((200..1000).contains(&start.elapsed().as_millis())); +} + +fn wait_absolute_timeout() { + let start = Instant::now(); + + // Get the current monotonic timestamp as timespec. + let mut timeout = unsafe { + let mut now: MaybeUninit = MaybeUninit::uninit(); + assert_eq!(libc::clock_gettime(libc::CLOCK_MONOTONIC, now.as_mut_ptr()), 0); + now.assume_init() + }; + + // Add 200ms. + timeout.tv_nsec += 200_000_000; + if timeout.tv_nsec > 1_000_000_000 { + timeout.tv_nsec -= 1_000_000_000; + timeout.tv_sec += 1; + } + + let futex: i32 = 123; + + // Wait for 200ms from now, with nobody waking us up early. + unsafe { + assert_eq!( + libc::syscall( + libc::SYS_futex, + &futex as *const i32, + libc::FUTEX_WAIT_BITSET, + 123, + &timeout, + 0usize, + u32::MAX, + ), + -1, + ); + assert_eq!(*libc::__errno_location(), libc::ETIMEDOUT); + } + + assert!((200..1000).contains(&start.elapsed().as_millis())); +} + +fn wait_wake() { + let start = Instant::now(); + + static mut FUTEX: i32 = 0; + + let t = thread::spawn(move || { + thread::sleep(Duration::from_millis(200)); + unsafe { + assert_eq!( + libc::syscall( + libc::SYS_futex, + &FUTEX as *const i32, + libc::FUTEX_WAKE, + 10, // Wake up at most 10 threads. + ), + 1, // Woken up one thread. + ); + } + }); + + unsafe { + assert_eq!( + libc::syscall( + libc::SYS_futex, + &FUTEX as *const i32, + libc::FUTEX_WAIT, + 0, + ptr::null::(), + ), + 0, + ); + } + + assert!((200..1000).contains(&start.elapsed().as_millis())); + t.join().unwrap(); +} + +fn wait_wake_bitset() { + let start = Instant::now(); + + static mut FUTEX: i32 = 0; + + let t = thread::spawn(move || { + thread::sleep(Duration::from_millis(200)); + unsafe { + assert_eq!( + libc::syscall( + libc::SYS_futex, + &FUTEX as *const i32, + libc::FUTEX_WAKE_BITSET, + 10, // Wake up at most 10 threads. + ptr::null::(), + 0usize, + 0b1001, // bitset + ), + 0, // Didn't match any thread. + ); + } + thread::sleep(Duration::from_millis(200)); + unsafe { + assert_eq!( + libc::syscall( + libc::SYS_futex, + &FUTEX as *const i32, + libc::FUTEX_WAKE_BITSET, + 10, // Wake up at most 10 threads. + ptr::null::(), + 0usize, + 0b0110, // bitset + ), + 1, // Woken up one thread. + ); + } + }); + + unsafe { + assert_eq!( + libc::syscall( + libc::SYS_futex, + &FUTEX as *const i32, + libc::FUTEX_WAIT_BITSET, + 0, + ptr::null::(), + 0usize, + 0b0100, // bitset + ), + 0, + ); + } + + assert!((400..1000).contains(&start.elapsed().as_millis())); + t.join().unwrap(); +} + +fn concurrent_wait_wake() { + const FREE: i32 = 0; + const HELD: i32 = 1; + + static FUTEX: AtomicI32 = AtomicI32::new(0); + static mut DATA: i32 = 0; + static WOKEN: AtomicI32 = AtomicI32::new(0); + + let rounds = 50; + for _ in 0..rounds { + unsafe { DATA = 0 }; // Reset + // Suppose the main thread is holding a lock implemented using futex... + FUTEX.store(HELD, Ordering::Relaxed); + + let t = thread::spawn(move || { + // If this syscall runs first, then we'll be woken up by + // the main thread's FUTEX_WAKE, and all is fine. + // + // If this sycall runs after the main thread's store + // and FUTEX_WAKE, the syscall must observe that + // the FUTEX is FREE != HELD and return without waiting + // or we'll deadlock. + unsafe { + let ret = libc::syscall( + libc::SYS_futex, + &FUTEX as *const AtomicI32, + libc::FUTEX_WAIT, + HELD, + ptr::null::(), + ); + if ret == 0 { + // We actually slept. And then woke up again. So we should be ordered-after + // what happened-before the FUTEX_WAKE. So this is not a race. + assert_eq!(DATA, 1); + // Also remember that this happened at least once. + WOKEN.fetch_add(1, Ordering::Relaxed); + } + } + }); + // Increase the chance that the other thread actually goes to sleep. + // (5 yields in a loop seem to make that happen around 40% of the time.) + for _ in 0..5 { + thread::yield_now(); + } + + FUTEX.store(FREE, Ordering::Relaxed); + unsafe { + DATA = 1; + libc::syscall(libc::SYS_futex, &FUTEX as *const AtomicI32, libc::FUTEX_WAKE, 1); + } + + t.join().unwrap(); + } + + // Make sure we got the interesting case (of having woken a thread) at least once, but not *each* time. + let woken = WOKEN.load(Ordering::Relaxed); + //eprintln!("waking happened {woken} times"); + assert!(woken > 0 && woken < rounds); +} + +fn main() { + wake_nobody(); + wake_dangling(); + wait_wrong_val(); + wait_timeout(); + wait_absolute_timeout(); + wait_wake(); + wait_wake_bitset(); + concurrent_wait_wake(); +} diff --git a/src/tools/miri/tests/pass-dep/concurrency/tls_pthread_drop_order.rs b/src/tools/miri/tests/pass-dep/concurrency/tls_pthread_drop_order.rs new file mode 100644 index 0000000000000..6516396ac5404 --- /dev/null +++ b/src/tools/miri/tests/pass-dep/concurrency/tls_pthread_drop_order.rs @@ -0,0 +1,73 @@ +//@ignore-target-windows: No libc on Windows +//! Test that pthread_key destructors are run in the right order. +//! Note that these are *not* used by actual `thread_local!` on Linux! Those use +//! `thread_local_dtor::register_dtor` from the stdlib instead. In Miri this hits the fallback path +//! in `register_dtor_fallback`, which uses a *single* pthread_key to manage a thread-local list of +//! dtors to call. + +use std::mem; +use std::ptr; + +pub type Key = libc::pthread_key_t; + +static mut RECORD: usize = 0; +static mut KEYS: [Key; 2] = [0; 2]; +static mut GLOBALS: [u64; 2] = [1, 0]; + +static mut CANNARY: *mut u64 = ptr::null_mut(); // this serves as a cannary: if TLS dtors are not run properly, this will not get deallocated, making the test fail. + +pub unsafe fn create(dtor: Option) -> Key { + let mut key = 0; + assert_eq!(libc::pthread_key_create(&mut key, mem::transmute(dtor)), 0); + key +} + +pub unsafe fn set(key: Key, value: *mut u8) { + let r = libc::pthread_setspecific(key, value as *mut _); + assert_eq!(r, 0); +} + +pub fn record(r: usize) { + assert!(r < 10); + unsafe { RECORD = RECORD * 10 + r }; +} + +unsafe extern "C" fn dtor(ptr: *mut u64) { + assert!(CANNARY != ptr::null_mut()); // make sure we do not get run too often + let val = *ptr; + + let which_key = + GLOBALS.iter().position(|global| global as *const _ == ptr).expect("Should find my global"); + record(which_key); + + if val > 0 { + *ptr = val - 1; + set(KEYS[which_key], ptr as *mut _); + } + + // Check if the records matches what we expect. If yes, clear the cannary. + // If the record is wrong, the cannary will never get cleared, leading to a leak -> test fails. + // If the record is incomplete (i.e., more dtor calls happen), the check at the beginning of this function will fail -> test fails. + // The correct sequence is: First key 0, then key 1, then key 0. + // Note that this relies on dtor order, which is not specified by POSIX, but seems to be + // consistent between Miri and Linux currently (as of Aug 2022). + if RECORD == 0_1_0 { + drop(Box::from_raw(CANNARY)); + CANNARY = ptr::null_mut(); + } +} + +fn main() { + unsafe { + create(None); // check that the no-dtor case works + + // Initialize the keys we use to check destructor ordering + for (key, global) in KEYS.iter_mut().zip(GLOBALS.iter_mut()) { + *key = create(Some(mem::transmute(dtor as unsafe extern "C" fn(*mut u64)))); + set(*key, global as *mut _ as *mut u8); + } + + // Initialize cannary + CANNARY = Box::into_raw(Box::new(0u64)); + } +} diff --git a/src/tools/miri/tests/pass-dep/foreign-fn-linkname.rs b/src/tools/miri/tests/pass-dep/foreign-fn-linkname.rs new file mode 100644 index 0000000000000..9f090a4eff5d8 --- /dev/null +++ b/src/tools/miri/tests/pass-dep/foreign-fn-linkname.rs @@ -0,0 +1,20 @@ +use std::ffi::CString; + +mod mlibc { + use libc::{c_char, size_t}; + extern "C" { + #[link_name = "strlen"] + pub fn my_strlen(str: *const c_char) -> size_t; + } +} + +fn strlen(str: String) -> usize { + // C string is terminated with a zero + let s = CString::new(str).unwrap(); + unsafe { mlibc::my_strlen(s.as_ptr()) as usize } +} + +pub fn main() { + let len = strlen("Rust".to_string()); + assert_eq!(len, 4); +} diff --git a/src/tools/miri/tests/pass-dep/malloc.rs b/src/tools/miri/tests/pass-dep/malloc.rs new file mode 100644 index 0000000000000..f5e014c000d15 --- /dev/null +++ b/src/tools/miri/tests/pass-dep/malloc.rs @@ -0,0 +1,49 @@ +//@ignore-target-windows: No libc on Windows + +use core::{ptr, slice}; + +fn main() { + // Test that small allocations sometimes *are* not very aligned. + let saw_unaligned = (0..64).any(|_| unsafe { + let p = libc::malloc(3); + libc::free(p); + (p as usize) % 4 != 0 // find any that this is *not* 4-aligned + }); + assert!(saw_unaligned); + + unsafe { + // Use calloc for initialized memory + let p1 = libc::calloc(20, 1); + + // old size < new size + let p2 = libc::realloc(p1, 40); + let slice = slice::from_raw_parts(p2 as *const u8, 20); + assert_eq!(&slice, &[0_u8; 20]); + + // old size == new size + let p3 = libc::realloc(p2, 40); + let slice = slice::from_raw_parts(p3 as *const u8, 20); + assert_eq!(&slice, &[0_u8; 20]); + + // old size > new size + let p4 = libc::realloc(p3, 10); + let slice = slice::from_raw_parts(p4 as *const u8, 10); + assert_eq!(&slice, &[0_u8; 10]); + + libc::free(p4); + } + + unsafe { + let p1 = libc::malloc(20); + + let p2 = libc::realloc(p1, 0); + assert!(p2.is_null()); + } + + unsafe { + let p1 = libc::realloc(ptr::null_mut(), 20); + assert!(!p1.is_null()); + + libc::free(p1); + } +} diff --git a/src/tools/miri/tests/pass-dep/num_cpus.rs b/src/tools/miri/tests/pass-dep/num_cpus.rs new file mode 100644 index 0000000000000..84339feb11e17 --- /dev/null +++ b/src/tools/miri/tests/pass-dep/num_cpus.rs @@ -0,0 +1,5 @@ +//@compile-flags: -Zmiri-disable-isolation + +fn main() { + assert_eq!(num_cpus::get(), 1); +} diff --git a/src/tools/miri/tests/pass-dep/page_size.rs b/src/tools/miri/tests/pass-dep/page_size.rs new file mode 100644 index 0000000000000..cdcabf3333814 --- /dev/null +++ b/src/tools/miri/tests/pass-dep/page_size.rs @@ -0,0 +1,6 @@ +fn main() { + let page_size = page_size::get(); + + // In particular, this checks that it is not 0. + assert!(page_size.is_power_of_two(), "page size not a power of two: {}", page_size); +} diff --git a/src/tools/miri/tests/pass-dep/random.rs b/src/tools/miri/tests/pass-dep/random.rs new file mode 100644 index 0000000000000..5eccf3b0ea115 --- /dev/null +++ b/src/tools/miri/tests/pass-dep/random.rs @@ -0,0 +1,22 @@ +// mac-os `getrandom_1` does some pointer shenanigans +//@compile-flags: -Zmiri-permissive-provenance +use rand::{rngs::SmallRng, Rng, SeedableRng}; + +fn main() { + // Test `getrandom` directly (in multiple different versions). + let mut data = vec![0; 16]; + getrandom_1::getrandom(&mut data).unwrap(); + getrandom_2::getrandom(&mut data).unwrap(); + + // Try seeding with "real" entropy. + let mut rng = SmallRng::from_entropy(); + let _val = rng.gen::(); + let _val = rng.gen::(); + let _val = rng.gen::(); + + // Also try per-thread RNG. + let mut rng = rand::thread_rng(); + let _val = rng.gen::(); + let _val = rng.gen::(); + let _val = rng.gen::(); +} diff --git a/src/tools/miri/tests/pass-dep/regions-mock-trans.rs b/src/tools/miri/tests/pass-dep/regions-mock-trans.rs new file mode 100644 index 0000000000000..57f1b75f4d52b --- /dev/null +++ b/src/tools/miri/tests/pass-dep/regions-mock-trans.rs @@ -0,0 +1,45 @@ +use std::mem; + +struct Arena(()); + +struct Bcx<'a> { + fcx: &'a Fcx<'a>, +} + +#[allow(dead_code)] +struct Fcx<'a> { + arena: &'a Arena, + ccx: &'a Ccx, +} + +#[allow(dead_code)] +struct Ccx { + x: isize, +} + +fn alloc<'a>(_bcx: &'a Arena) -> &'a mut Bcx<'a> { + unsafe { mem::transmute(libc::malloc(mem::size_of::>() as libc::size_t)) } +} + +fn h<'a>(bcx: &'a Bcx<'a>) -> &'a mut Bcx<'a> { + return alloc(bcx.fcx.arena); +} + +fn g(fcx: &Fcx) { + let bcx = Bcx { fcx: fcx }; + let bcx2 = h(&bcx); + unsafe { + libc::free(mem::transmute(bcx2)); + } +} + +fn f(ccx: &Ccx) { + let a = Arena(()); + let fcx = Fcx { arena: &a, ccx: ccx }; + return g(&fcx); +} + +pub fn main() { + let ccx = Ccx { x: 0 }; + f(&ccx); +} diff --git a/src/tools/miri/tests/pass-dep/shims/env-cleanup-data-race.rs b/src/tools/miri/tests/pass-dep/shims/env-cleanup-data-race.rs new file mode 100644 index 0000000000000..d36ffe70321b4 --- /dev/null +++ b/src/tools/miri/tests/pass-dep/shims/env-cleanup-data-race.rs @@ -0,0 +1,23 @@ +//@compile-flags: -Zmiri-disable-isolation -Zmiri-preemption-rate=0 +//@ignore-target-windows: No libc on Windows + +use std::ffi::CStr; +use std::ffi::CString; +use std::thread; + +fn main() { + unsafe { + thread::spawn(|| { + // Access the environment in another thread without taking the env lock + let k = CString::new("MIRI_ENV_VAR_TEST".as_bytes()).unwrap(); + let s = libc::getenv(k.as_ptr()) as *const libc::c_char; + if s.is_null() { + panic!("null"); + } + let _s = String::from_utf8_lossy(CStr::from_ptr(s).to_bytes()); + }); + thread::yield_now(); + // After the main thread exits, env vars will be cleaned up -- but because we have not *joined* + // the other thread, those accesses technically race with those in the other thread. + } +} diff --git a/src/tools/miri/tests/pass-dep/shims/fs.rs b/src/tools/miri/tests/pass-dep/shims/fs.rs new file mode 100644 index 0000000000000..9faced0291612 --- /dev/null +++ b/src/tools/miri/tests/pass-dep/shims/fs.rs @@ -0,0 +1,445 @@ +//@ignore-target-windows: File handling is not implemented yet +//@compile-flags: -Zmiri-disable-isolation + +#![feature(io_error_more)] +#![feature(io_error_uncategorized)] + +use std::ffi::CString; +use std::fs::{ + create_dir, read_dir, read_link, remove_dir, remove_dir_all, remove_file, rename, File, + OpenOptions, +}; +use std::io::{Error, ErrorKind, Read, Result, Seek, SeekFrom, Write}; +use std::path::{Path, PathBuf}; + +fn main() { + test_file(); + test_file_clone(); + test_file_create_new(); + test_seek(); + test_metadata(); + test_file_set_len(); + test_file_sync(); + test_symlink(); + test_errors(); + test_rename(); + test_directory(); + test_canonicalize(); + test_dup_stdout_stderr(); + test_from_raw_os_error(); + + // These all require unix, if the test is changed to no longer `ignore-windows`, move these to a unix test + test_file_open_unix_allow_two_args(); + test_file_open_unix_needs_three_args(); + test_file_open_unix_extra_third_arg(); +} + +fn tmp() -> PathBuf { + std::env::var("MIRI_TEMP") + .map(|tmp| { + // MIRI_TEMP is set outside of our emulated + // program, so it may have path separators that don't + // correspond to our target platform. We normalize them here + // before constructing a `PathBuf` + + #[cfg(windows)] + return PathBuf::from(tmp.replace("/", "\\")); + + #[cfg(not(windows))] + return PathBuf::from(tmp.replace("\\", "/")); + }) + .unwrap_or_else(|_| std::env::temp_dir()) +} + +/// Prepare: compute filename and make sure the file does not exist. +fn prepare(filename: &str) -> PathBuf { + let path = tmp().join(filename); + // Clean the paths for robustness. + remove_file(&path).ok(); + path +} + +/// Prepare directory: compute directory name and make sure it does not exist. +fn prepare_dir(dirname: &str) -> PathBuf { + let path = tmp().join(&dirname); + // Clean the directory for robustness. + remove_dir_all(&path).ok(); + path +} + +/// Prepare like above, and also write some initial content to the file. +fn prepare_with_content(filename: &str, content: &[u8]) -> PathBuf { + let path = prepare(filename); + let mut file = File::create(&path).unwrap(); + file.write(content).unwrap(); + path +} + +fn test_file() { + let bytes = b"Hello, World!\n"; + let path = prepare("miri_test_fs_file.txt"); + + // Test creating, writing and closing a file (closing is tested when `file` is dropped). + let mut file = File::create(&path).unwrap(); + // Writing 0 bytes should not change the file contents. + file.write(&mut []).unwrap(); + assert_eq!(file.metadata().unwrap().len(), 0); + + file.write(bytes).unwrap(); + assert_eq!(file.metadata().unwrap().len(), bytes.len() as u64); + // Test opening, reading and closing a file. + let mut file = File::open(&path).unwrap(); + let mut contents = Vec::new(); + // Reading 0 bytes should not move the file pointer. + file.read(&mut []).unwrap(); + // Reading until EOF should get the whole text. + file.read_to_end(&mut contents).unwrap(); + assert_eq!(bytes, contents.as_slice()); + + // Removing file should succeed. + remove_file(&path).unwrap(); +} + +fn test_file_open_unix_allow_two_args() { + use std::os::unix::ffi::OsStrExt; + + let path = prepare_with_content("test_file_open_unix_allow_two_args.txt", &[]); + + let mut name = path.into_os_string(); + name.push("\0"); + let name_ptr = name.as_bytes().as_ptr().cast::(); + let _fd = unsafe { libc::open(name_ptr, libc::O_RDONLY) }; +} + +fn test_file_open_unix_needs_three_args() { + use std::os::unix::ffi::OsStrExt; + + let path = prepare_with_content("test_file_open_unix_needs_three_args.txt", &[]); + + let mut name = path.into_os_string(); + name.push("\0"); + let name_ptr = name.as_bytes().as_ptr().cast::(); + let _fd = unsafe { libc::open(name_ptr, libc::O_CREAT, 0o666) }; +} + +fn test_file_open_unix_extra_third_arg() { + use std::os::unix::ffi::OsStrExt; + + let path = prepare_with_content("test_file_open_unix_extra_third_arg.txt", &[]); + + let mut name = path.into_os_string(); + name.push("\0"); + let name_ptr = name.as_bytes().as_ptr().cast::(); + let _fd = unsafe { libc::open(name_ptr, libc::O_RDONLY, 42) }; +} + +fn test_file_clone() { + let bytes = b"Hello, World!\n"; + let path = prepare_with_content("miri_test_fs_file_clone.txt", bytes); + + // Cloning a file should be successful. + let file = File::open(&path).unwrap(); + let mut cloned = file.try_clone().unwrap(); + // Reading from a cloned file should get the same text. + let mut contents = Vec::new(); + cloned.read_to_end(&mut contents).unwrap(); + assert_eq!(bytes, contents.as_slice()); + + // Removing file should succeed. + remove_file(&path).unwrap(); +} + +fn test_file_create_new() { + let path = prepare("miri_test_fs_file_create_new.txt"); + + // Creating a new file that doesn't yet exist should succeed. + OpenOptions::new().write(true).create_new(true).open(&path).unwrap(); + // Creating a new file that already exists should fail. + assert_eq!( + ErrorKind::AlreadyExists, + OpenOptions::new().write(true).create_new(true).open(&path).unwrap_err().kind() + ); + // Optionally creating a new file that already exists should succeed. + OpenOptions::new().write(true).create(true).open(&path).unwrap(); + + // Clean up + remove_file(&path).unwrap(); +} + +fn test_seek() { + let bytes = b"Hello, entire World!\n"; + let path = prepare_with_content("miri_test_fs_seek.txt", bytes); + + let mut file = File::open(&path).unwrap(); + let mut contents = Vec::new(); + file.read_to_end(&mut contents).unwrap(); + assert_eq!(bytes, contents.as_slice()); + // Test that seeking to the beginning and reading until EOF gets the text again. + file.seek(SeekFrom::Start(0)).unwrap(); + let mut contents = Vec::new(); + file.read_to_end(&mut contents).unwrap(); + assert_eq!(bytes, contents.as_slice()); + // Test seeking relative to the end of the file. + file.seek(SeekFrom::End(-1)).unwrap(); + let mut contents = Vec::new(); + file.read_to_end(&mut contents).unwrap(); + assert_eq!(&bytes[bytes.len() - 1..], contents.as_slice()); + // Test seeking relative to the current position. + file.seek(SeekFrom::Start(5)).unwrap(); + file.seek(SeekFrom::Current(-3)).unwrap(); + let mut contents = Vec::new(); + file.read_to_end(&mut contents).unwrap(); + assert_eq!(&bytes[2..], contents.as_slice()); + + // Removing file should succeed. + remove_file(&path).unwrap(); +} + +fn check_metadata(bytes: &[u8], path: &Path) -> Result<()> { + // Test that the file metadata is correct. + let metadata = path.metadata()?; + // `path` should point to a file. + assert!(metadata.is_file()); + // The size of the file must be equal to the number of written bytes. + assert_eq!(bytes.len() as u64, metadata.len()); + Ok(()) +} + +fn test_metadata() { + let bytes = b"Hello, meta-World!\n"; + let path = prepare_with_content("miri_test_fs_metadata.txt", bytes); + + // Test that metadata of an absolute path is correct. + check_metadata(bytes, &path).unwrap(); + // Test that metadata of a relative path is correct. + std::env::set_current_dir(path.parent().unwrap()).unwrap(); + check_metadata(bytes, Path::new(path.file_name().unwrap())).unwrap(); + + // Removing file should succeed. + remove_file(&path).unwrap(); +} + +fn test_file_set_len() { + let bytes = b"Hello, World!\n"; + let path = prepare_with_content("miri_test_fs_set_len.txt", bytes); + + // Test extending the file + let mut file = OpenOptions::new().read(true).write(true).open(&path).unwrap(); + let bytes_extended = b"Hello, World!\n\x00\x00\x00\x00\x00\x00"; + file.set_len(20).unwrap(); + let mut contents = Vec::new(); + file.read_to_end(&mut contents).unwrap(); + assert_eq!(bytes_extended, contents.as_slice()); + + // Test truncating the file + file.seek(SeekFrom::Start(0)).unwrap(); + file.set_len(10).unwrap(); + let mut contents = Vec::new(); + file.read_to_end(&mut contents).unwrap(); + assert_eq!(&bytes[..10], contents.as_slice()); + + // Can't use set_len on a file not opened for writing + let file = OpenOptions::new().read(true).open(&path).unwrap(); + assert_eq!(ErrorKind::InvalidInput, file.set_len(14).unwrap_err().kind()); + + remove_file(&path).unwrap(); +} + +fn test_file_sync() { + let bytes = b"Hello, World!\n"; + let path = prepare_with_content("miri_test_fs_sync.txt", bytes); + + // Test that we can call sync_data and sync_all (can't readily test effects of this operation) + let file = OpenOptions::new().write(true).open(&path).unwrap(); + file.sync_data().unwrap(); + file.sync_all().unwrap(); + + // Test that we can call sync_data and sync_all on a file opened for reading. + let file = File::open(&path).unwrap(); + file.sync_data().unwrap(); + file.sync_all().unwrap(); + + remove_file(&path).unwrap(); +} + +fn test_symlink() { + let bytes = b"Hello, World!\n"; + let path = prepare_with_content("miri_test_fs_link_target.txt", bytes); + let symlink_path = prepare("miri_test_fs_symlink.txt"); + + // Creating a symbolic link should succeed. + #[cfg(unix)] + std::os::unix::fs::symlink(&path, &symlink_path).unwrap(); + #[cfg(windows)] + std::os::windows::fs::symlink_file(&path, &symlink_path).unwrap(); + // Test that the symbolic link has the same contents as the file. + let mut symlink_file = File::open(&symlink_path).unwrap(); + let mut contents = Vec::new(); + symlink_file.read_to_end(&mut contents).unwrap(); + assert_eq!(bytes, contents.as_slice()); + + #[cfg(unix)] + { + use std::os::unix::ffi::OsStrExt; + + let expected_path = path.as_os_str().as_bytes(); + + // Test that the expected string gets written to a buffer of proper + // length, and that a trailing null byte is not written. + let symlink_c_str = CString::new(symlink_path.as_os_str().as_bytes()).unwrap(); + let symlink_c_ptr = symlink_c_str.as_ptr(); + + // Make the buf one byte larger than it needs to be, + // and check that the last byte is not overwritten. + let mut large_buf = vec![0xFF; expected_path.len() + 1]; + let res = unsafe { + libc::readlink(symlink_c_ptr, large_buf.as_mut_ptr().cast(), large_buf.len()) + }; + // Check that the resovled path was properly written into the buf. + assert_eq!(&large_buf[..(large_buf.len() - 1)], expected_path); + assert_eq!(large_buf.last(), Some(&0xFF)); + assert_eq!(res, large_buf.len() as isize - 1); + + // Test that the resolved path is truncated if the provided buffer + // is too small. + let mut small_buf = [0u8; 2]; + let res = unsafe { + libc::readlink(symlink_c_ptr, small_buf.as_mut_ptr().cast(), small_buf.len()) + }; + assert_eq!(small_buf, &expected_path[..small_buf.len()]); + assert_eq!(res, small_buf.len() as isize); + + // Test that we report a proper error for a missing path. + let bad_path = CString::new("MIRI_MISSING_FILE_NAME").unwrap(); + let res = unsafe { + libc::readlink(bad_path.as_ptr(), small_buf.as_mut_ptr().cast(), small_buf.len()) + }; + assert_eq!(res, -1); + assert_eq!(Error::last_os_error().kind(), ErrorKind::NotFound); + } + + // Test that metadata of a symbolic link (i.e., the file it points to) is correct. + check_metadata(bytes, &symlink_path).unwrap(); + // Test that the metadata of a symbolic link is correct when not following it. + assert!(symlink_path.symlink_metadata().unwrap().file_type().is_symlink()); + // Check that we can follow the link. + assert_eq!(read_link(&symlink_path).unwrap(), path); + // Removing symbolic link should succeed. + remove_file(&symlink_path).unwrap(); + + // Removing file should succeed. + remove_file(&path).unwrap(); +} + +fn test_errors() { + let bytes = b"Hello, World!\n"; + let path = prepare("miri_test_fs_errors.txt"); + + // The following tests also check that the `__errno_location()` shim is working properly. + // 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()); + // 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. + assert_eq!(ErrorKind::NotFound, check_metadata(bytes, &path).unwrap_err().kind()); +} + +fn test_rename() { + // Renaming a file should succeed. + let path1 = prepare("miri_test_fs_rename_source.txt"); + let path2 = prepare("miri_test_fs_rename_destination.txt"); + + let file = File::create(&path1).unwrap(); + drop(file); + + // Renaming should succeed + rename(&path1, &path2).unwrap(); + // Check that the old file path isn't present + assert_eq!(ErrorKind::NotFound, path1.metadata().unwrap_err().kind()); + // Check that the file has moved successfully + assert!(path2.metadata().unwrap().is_file()); + + // Renaming a nonexistent file should fail + assert_eq!(ErrorKind::NotFound, rename(&path1, &path2).unwrap_err().kind()); + + remove_file(&path2).unwrap(); +} + +fn test_canonicalize() { + use std::fs::canonicalize; + let dir_path = prepare_dir("miri_test_fs_dir"); + create_dir(&dir_path).unwrap(); + let path = dir_path.join("test_file"); + drop(File::create(&path).unwrap()); + + let p = canonicalize(format!("{}/./test_file", dir_path.to_string_lossy())).unwrap(); + assert_eq!(p.to_string_lossy().find('.'), None); + + remove_dir_all(&dir_path).unwrap(); + + // Make sure we get an error for long paths. + use std::convert::TryInto; + let too_long = "x/".repeat(libc::PATH_MAX.try_into().unwrap()); + assert!(canonicalize(too_long).is_err()); +} + +fn test_directory() { + let dir_path = prepare_dir("miri_test_fs_dir"); + // Creating a directory should succeed. + create_dir(&dir_path).unwrap(); + // Test that the metadata of a directory is correct. + assert!(dir_path.metadata().unwrap().is_dir()); + // Creating a directory when it already exists should fail. + assert_eq!(ErrorKind::AlreadyExists, create_dir(&dir_path).unwrap_err().kind()); + + // Create some files inside the directory + let path_1 = dir_path.join("test_file_1"); + drop(File::create(&path_1).unwrap()); + let path_2 = dir_path.join("test_file_2"); + drop(File::create(&path_2).unwrap()); + // Test that the files are present inside the directory + let dir_iter = read_dir(&dir_path).unwrap(); + let mut file_names = dir_iter.map(|e| e.unwrap().file_name()).collect::>(); + file_names.sort_unstable(); + assert_eq!(file_names, vec!["test_file_1", "test_file_2"]); + // Deleting the directory should fail, since it is not empty. + assert_eq!(ErrorKind::DirectoryNotEmpty, remove_dir(&dir_path).unwrap_err().kind()); + // Clean up the files in the directory + remove_file(&path_1).unwrap(); + remove_file(&path_2).unwrap(); + // Now there should be nothing left in the directory. + let dir_iter = read_dir(&dir_path).unwrap(); + let file_names = dir_iter.map(|e| e.unwrap().file_name()).collect::>(); + assert!(file_names.is_empty()); + + // Deleting the directory should succeed. + remove_dir(&dir_path).unwrap(); + // Reading the metadata of a non-existent directory should fail with a "not found" error. + assert_eq!(ErrorKind::NotFound, check_metadata(&[], &dir_path).unwrap_err().kind()); + + // To test remove_dir_all, re-create the directory with a file and a directory in it. + create_dir(&dir_path).unwrap(); + drop(File::create(&path_1).unwrap()); + create_dir(&path_2).unwrap(); + remove_dir_all(&dir_path).unwrap(); +} + +fn test_dup_stdout_stderr() { + let bytes = b"hello dup fd\n"; + unsafe { + let new_stdout = libc::fcntl(1, libc::F_DUPFD, 0); + let new_stderr = libc::fcntl(2, libc::F_DUPFD, 0); + libc::write(new_stdout, bytes.as_ptr() as *const libc::c_void, bytes.len()); + libc::write(new_stderr, bytes.as_ptr() as *const libc::c_void, bytes.len()); + } +} + +fn test_from_raw_os_error() { + let code = 6; // not a code that std or Miri know + let error = Error::from_raw_os_error(code); + assert!(matches!(error.kind(), ErrorKind::Uncategorized)); + // Make sure we can also format this. + format!("{error:?}"); +} diff --git a/src/tools/miri/tests/pass-dep/shims/fs.stderr b/src/tools/miri/tests/pass-dep/shims/fs.stderr new file mode 100644 index 0000000000000..b6fa69e3d5d2e --- /dev/null +++ b/src/tools/miri/tests/pass-dep/shims/fs.stderr @@ -0,0 +1 @@ +hello dup fd diff --git a/src/tools/miri/tests/pass-dep/shims/fs.stdout b/src/tools/miri/tests/pass-dep/shims/fs.stdout new file mode 100644 index 0000000000000..b6fa69e3d5d2e --- /dev/null +++ b/src/tools/miri/tests/pass-dep/shims/fs.stdout @@ -0,0 +1 @@ +hello dup fd diff --git a/src/tools/miri/tests/pass-dep/shims/fs_with_isolation.rs b/src/tools/miri/tests/pass-dep/shims/fs_with_isolation.rs new file mode 100644 index 0000000000000..f5420dbc5538e --- /dev/null +++ b/src/tools/miri/tests/pass-dep/shims/fs_with_isolation.rs @@ -0,0 +1,53 @@ +//@ignore-target-windows: File handling is not implemented yet +//@compile-flags: -Zmiri-isolation-error=warn-nobacktrace +//@normalize-stderr-test: "(stat(x)?)" -> "$$STAT" + +use std::ffi::CString; +use std::fs::{self, File}; +use std::io::{Error, ErrorKind}; +use std::os::unix; + +fn main() { + // test `open` + assert_eq!(File::create("foo.txt").unwrap_err().kind(), ErrorKind::PermissionDenied); + + // test `fcntl` + unsafe { + assert_eq!(libc::fcntl(1, libc::F_DUPFD, 0), -1); + assert_eq!(Error::last_os_error().raw_os_error(), Some(libc::EPERM)); + } + + // test `unlink` + assert_eq!(fs::remove_file("foo.txt").unwrap_err().kind(), ErrorKind::PermissionDenied); + + // test `symlink` + assert_eq!( + unix::fs::symlink("foo.txt", "foo_link.txt").unwrap_err().kind(), + ErrorKind::PermissionDenied + ); + + // test `readlink` + let symlink_c_str = CString::new("foo.txt").unwrap(); + let mut buf = vec![0; "foo_link.txt".len() + 1]; + unsafe { + assert_eq!(libc::readlink(symlink_c_str.as_ptr(), buf.as_mut_ptr(), buf.len()), -1); + assert_eq!(Error::last_os_error().raw_os_error(), Some(libc::EACCES)); + } + + // test `stat` + assert_eq!(fs::metadata("foo.txt").unwrap_err().kind(), ErrorKind::PermissionDenied); + assert_eq!(Error::last_os_error().raw_os_error(), Some(libc::EACCES)); + + // test `rename` + assert_eq!(fs::rename("a.txt", "b.txt").unwrap_err().kind(), ErrorKind::PermissionDenied); + + // test `mkdir` + assert_eq!(fs::create_dir("foo/bar").unwrap_err().kind(), ErrorKind::PermissionDenied); + + // test `rmdir` + assert_eq!(fs::remove_dir("foo/bar").unwrap_err().kind(), ErrorKind::PermissionDenied); + + // test `opendir` + assert_eq!(fs::read_dir("foo/bar").unwrap_err().kind(), ErrorKind::PermissionDenied); + assert_eq!(Error::last_os_error().raw_os_error(), Some(libc::EACCES)); +} diff --git a/src/tools/miri/tests/pass-dep/shims/fs_with_isolation.stderr b/src/tools/miri/tests/pass-dep/shims/fs_with_isolation.stderr new file mode 100644 index 0000000000000..ad75e42831b0a --- /dev/null +++ b/src/tools/miri/tests/pass-dep/shims/fs_with_isolation.stderr @@ -0,0 +1,20 @@ +warning: `open` was made to return an error due to isolation + +warning: `fcntl` was made to return an error due to isolation + +warning: `unlink` was made to return an error due to isolation + +warning: `symlink` was made to return an error due to isolation + +warning: `readlink` was made to return an error due to isolation + +warning: `$STAT` was made to return an error due to isolation + +warning: `rename` was made to return an error due to isolation + +warning: `mkdir` was made to return an error due to isolation + +warning: `rmdir` was made to return an error due to isolation + +warning: `opendir` was made to return an error due to isolation + diff --git a/src/tools/miri/tests/pass-dep/shims/libc-misc.rs b/src/tools/miri/tests/pass-dep/shims/libc-misc.rs new file mode 100644 index 0000000000000..a883a3d967a3c --- /dev/null +++ b/src/tools/miri/tests/pass-dep/shims/libc-misc.rs @@ -0,0 +1,305 @@ +//@ignore-target-windows: No libc on Windows +//@compile-flags: -Zmiri-disable-isolation +#![feature(io_error_more)] + +use std::fs::{remove_file, File}; +use std::os::unix::io::AsRawFd; +use std::path::PathBuf; + +fn tmp() -> PathBuf { + std::env::var("MIRI_TEMP") + .map(|tmp| { + // MIRI_TEMP is set outside of our emulated + // program, so it may have path separators that don't + // correspond to our target platform. We normalize them here + // before constructing a `PathBuf` + return PathBuf::from(tmp.replace("\\", "/")); + }) + .unwrap_or_else(|_| std::env::temp_dir()) +} + +/// Test allocating variant of `realpath`. +fn test_posix_realpath_alloc() { + use std::ffi::OsString; + use std::ffi::{CStr, CString}; + use std::os::unix::ffi::OsStrExt; + use std::os::unix::ffi::OsStringExt; + + let buf; + let path = tmp().join("miri_test_libc_posix_realpath_alloc"); + let c_path = CString::new(path.as_os_str().as_bytes()).expect("CString::new failed"); + + // Cleanup before test. + remove_file(&path).ok(); + // Create file. + drop(File::create(&path).unwrap()); + unsafe { + let r = libc::realpath(c_path.as_ptr(), std::ptr::null_mut()); + assert!(!r.is_null()); + buf = CStr::from_ptr(r).to_bytes().to_vec(); + libc::free(r as *mut _); + } + let canonical = PathBuf::from(OsString::from_vec(buf)); + assert_eq!(path.file_name(), canonical.file_name()); + + // Cleanup after test. + remove_file(&path).unwrap(); +} + +/// Test non-allocating variant of `realpath`. +fn test_posix_realpath_noalloc() { + use std::ffi::{CStr, CString}; + use std::os::unix::ffi::OsStrExt; + + let path = tmp().join("miri_test_libc_posix_realpath_noalloc"); + let c_path = CString::new(path.as_os_str().as_bytes()).expect("CString::new failed"); + + let mut v = vec![0; libc::PATH_MAX as usize]; + + // Cleanup before test. + remove_file(&path).ok(); + // Create file. + drop(File::create(&path).unwrap()); + unsafe { + let r = libc::realpath(c_path.as_ptr(), v.as_mut_ptr()); + assert!(!r.is_null()); + } + let c = unsafe { CStr::from_ptr(v.as_ptr()) }; + let canonical = PathBuf::from(c.to_str().expect("CStr to str")); + + assert_eq!(path.file_name(), canonical.file_name()); + + // Cleanup after test. + remove_file(&path).unwrap(); +} + +/// Test failure cases for `realpath`. +fn test_posix_realpath_errors() { + use std::ffi::CString; + use std::io::ErrorKind; + + // Test non-existent path returns an error. + let c_path = CString::new("./nothing_to_see_here").expect("CString::new failed"); + let r = unsafe { libc::realpath(c_path.as_ptr(), std::ptr::null_mut()) }; + assert!(r.is_null()); + let e = std::io::Error::last_os_error(); + assert_eq!(e.raw_os_error(), Some(libc::ENOENT)); + assert_eq!(e.kind(), ErrorKind::NotFound); +} + +#[cfg(any(target_os = "linux"))] +fn test_posix_fadvise() { + use std::convert::TryInto; + use std::io::Write; + + let path = tmp().join("miri_test_libc_posix_fadvise.txt"); + // Cleanup before test + remove_file(&path).ok(); + + // Set up an open file + let mut file = File::create(&path).unwrap(); + let bytes = b"Hello, World!\n"; + file.write(bytes).unwrap(); + + // Test calling posix_fadvise on a file. + let result = unsafe { + libc::posix_fadvise( + file.as_raw_fd(), + 0, + bytes.len().try_into().unwrap(), + libc::POSIX_FADV_DONTNEED, + ) + }; + drop(file); + remove_file(&path).unwrap(); + assert_eq!(result, 0); +} + +#[cfg(any(target_os = "linux"))] +fn test_sync_file_range() { + use std::io::Write; + + let path = tmp().join("miri_test_libc_sync_file_range.txt"); + // Cleanup before test. + remove_file(&path).ok(); + + // Write to a file. + let mut file = File::create(&path).unwrap(); + let bytes = b"Hello, World!\n"; + file.write(bytes).unwrap(); + + // Test calling sync_file_range on the file. + let result_1 = unsafe { + libc::sync_file_range( + file.as_raw_fd(), + 0, + 0, + libc::SYNC_FILE_RANGE_WAIT_BEFORE + | libc::SYNC_FILE_RANGE_WRITE + | libc::SYNC_FILE_RANGE_WAIT_AFTER, + ) + }; + drop(file); + + // Test calling sync_file_range on a file opened for reading. + let file = File::open(&path).unwrap(); + let result_2 = unsafe { + libc::sync_file_range( + file.as_raw_fd(), + 0, + 0, + libc::SYNC_FILE_RANGE_WAIT_BEFORE + | libc::SYNC_FILE_RANGE_WRITE + | libc::SYNC_FILE_RANGE_WAIT_AFTER, + ) + }; + drop(file); + + remove_file(&path).unwrap(); + assert_eq!(result_1, 0); + assert_eq!(result_2, 0); +} + +/// Tests whether each thread has its own `__errno_location`. +fn test_thread_local_errno() { + #[cfg(target_os = "linux")] + use libc::__errno_location; + #[cfg(any(target_os = "macos", target_os = "freebsd"))] + use libc::__error as __errno_location; + + unsafe { + *__errno_location() = 0xBEEF; + std::thread::spawn(|| { + assert_eq!(*__errno_location(), 0); + *__errno_location() = 0xBAD1DEA; + assert_eq!(*__errno_location(), 0xBAD1DEA); + }) + .join() + .unwrap(); + assert_eq!(*__errno_location(), 0xBEEF); + } +} + +/// Tests whether clock support exists at all +#[cfg(any(target_os = "linux"))] +fn test_clocks() { + let mut tp = std::mem::MaybeUninit::::uninit(); + let is_error = unsafe { libc::clock_gettime(libc::CLOCK_REALTIME, tp.as_mut_ptr()) }; + assert_eq!(is_error, 0); + let is_error = unsafe { libc::clock_gettime(libc::CLOCK_REALTIME_COARSE, tp.as_mut_ptr()) }; + assert_eq!(is_error, 0); + let is_error = unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC, tp.as_mut_ptr()) }; + assert_eq!(is_error, 0); + let is_error = unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC_COARSE, tp.as_mut_ptr()) }; + assert_eq!(is_error, 0); +} + +fn test_posix_gettimeofday() { + let mut tp = std::mem::MaybeUninit::::uninit(); + let tz = std::ptr::null_mut::(); + #[cfg(target_os = "macos")] // `tz` has a different type on macOS + let tz = tz as *mut libc::c_void; + let is_error = unsafe { libc::gettimeofday(tp.as_mut_ptr(), tz) }; + assert_eq!(is_error, 0); + let tv = unsafe { tp.assume_init() }; + assert!(tv.tv_sec > 0); + assert!(tv.tv_usec >= 0); // Theoretically this could be 0. + + // Test that non-null tz returns an error. + let mut tz = std::mem::MaybeUninit::::uninit(); + let tz_ptr = tz.as_mut_ptr(); + #[cfg(target_os = "macos")] // `tz` has a different type on macOS + let tz_ptr = tz_ptr as *mut libc::c_void; + let is_error = unsafe { libc::gettimeofday(tp.as_mut_ptr(), tz_ptr) }; + assert_eq!(is_error, -1); +} + +fn test_isatty() { + // Testing whether our isatty shim returns the right value would require controlling whether + // these streams are actually TTYs, which is hard. + // For now, we just check that these calls are supported at all. + unsafe { + libc::isatty(libc::STDIN_FILENO); + libc::isatty(libc::STDOUT_FILENO); + libc::isatty(libc::STDERR_FILENO); + + // But when we open a file, it is definitely not a TTY. + let path = tmp().join("notatty.txt"); + // Cleanup before test. + remove_file(&path).ok(); + let file = File::create(&path).unwrap(); + + assert_eq!(libc::isatty(file.as_raw_fd()), 0); + assert_eq!(std::io::Error::last_os_error().raw_os_error().unwrap(), libc::ENOTTY); + + // Cleanup after test. + drop(file); + remove_file(&path).unwrap(); + } +} + +fn test_posix_mkstemp() { + use std::ffi::CString; + use std::ffi::OsStr; + use std::os::unix::ffi::OsStrExt; + use std::os::unix::io::FromRawFd; + use std::path::Path; + + let valid_template = "fooXXXXXX"; + // C needs to own this as `mkstemp(3)` says: + // "Since it will be modified, `template` must not be a string constant, but + // should be declared as a character array." + // There seems to be no `as_mut_ptr` on `CString` so we need to use `into_raw`. + let ptr = CString::new(valid_template).unwrap().into_raw(); + let fd = unsafe { libc::mkstemp(ptr) }; + // Take ownership back in Rust to not leak memory. + let slice = unsafe { CString::from_raw(ptr) }; + assert!(fd > 0); + let osstr = OsStr::from_bytes(slice.to_bytes()); + let path: &Path = osstr.as_ref(); + let name = path.file_name().unwrap().to_string_lossy(); + assert!(name.ne("fooXXXXXX")); + assert!(name.starts_with("foo")); + assert_eq!(name.len(), 9); + assert_eq!( + name.chars().skip(3).filter(char::is_ascii_alphanumeric).collect::>().len(), + 6 + ); + let file = unsafe { File::from_raw_fd(fd) }; + assert!(file.set_len(0).is_ok()); + + let invalid_templates = vec!["foo", "barXX", "XXXXXXbaz", "whatXXXXXXever", "X"]; + for t in invalid_templates { + let ptr = CString::new(t).unwrap().into_raw(); + let fd = unsafe { libc::mkstemp(ptr) }; + let _ = unsafe { CString::from_raw(ptr) }; + // "On error, -1 is returned, and errno is set to + // indicate the error" + assert_eq!(fd, -1); + let e = std::io::Error::last_os_error(); + assert_eq!(e.raw_os_error(), Some(libc::EINVAL)); + assert_eq!(e.kind(), std::io::ErrorKind::InvalidInput); + } +} + +fn main() { + #[cfg(any(target_os = "linux"))] + test_posix_fadvise(); + + test_posix_gettimeofday(); + test_posix_mkstemp(); + + test_posix_realpath_alloc(); + test_posix_realpath_noalloc(); + test_posix_realpath_errors(); + + #[cfg(any(target_os = "linux"))] + test_sync_file_range(); + + test_thread_local_errno(); + + #[cfg(any(target_os = "linux"))] + test_clocks(); + + test_isatty(); +} diff --git a/src/tools/miri/tests/pass-dep/shims/linux-getrandom-without-isolation.rs b/src/tools/miri/tests/pass-dep/shims/linux-getrandom-without-isolation.rs new file mode 100644 index 0000000000000..349b447569a4b --- /dev/null +++ b/src/tools/miri/tests/pass-dep/shims/linux-getrandom-without-isolation.rs @@ -0,0 +1,41 @@ +//@only-target-linux +//@compile-flags: -Zmiri-disable-isolation + +use std::ptr; + +fn main() { + let mut buf = [0u8; 5]; + unsafe { + assert_eq!( + libc::syscall( + libc::SYS_getrandom, + ptr::null_mut::(), + 0 as libc::size_t, + 0 as libc::c_uint, + ), + 0, + ); + assert_eq!( + libc::syscall( + libc::SYS_getrandom, + buf.as_mut_ptr() as *mut libc::c_void, + 5 as libc::size_t, + 0 as libc::c_uint, + ), + 5, + ); + + assert_eq!( + libc::getrandom(ptr::null_mut::(), 0 as libc::size_t, 0 as libc::c_uint), + 0, + ); + assert_eq!( + libc::getrandom( + buf.as_mut_ptr() as *mut libc::c_void, + 5 as libc::size_t, + 0 as libc::c_uint, + ), + 5, + ); + } +} diff --git a/src/tools/miri/tests/pass-dep/shims/linux-getrandom.rs b/src/tools/miri/tests/pass-dep/shims/linux-getrandom.rs new file mode 100644 index 0000000000000..a1436c7319d33 --- /dev/null +++ b/src/tools/miri/tests/pass-dep/shims/linux-getrandom.rs @@ -0,0 +1,40 @@ +//@only-target-linux + +use std::ptr; + +fn main() { + let mut buf = [0u8; 5]; + unsafe { + assert_eq!( + libc::syscall( + libc::SYS_getrandom, + ptr::null_mut::(), + 0 as libc::size_t, + 0 as libc::c_uint, + ), + 0, + ); + assert_eq!( + libc::syscall( + libc::SYS_getrandom, + buf.as_mut_ptr() as *mut libc::c_void, + 5 as libc::size_t, + 0 as libc::c_uint, + ), + 5, + ); + + assert_eq!( + libc::getrandom(ptr::null_mut::(), 0 as libc::size_t, 0 as libc::c_uint), + 0, + ); + assert_eq!( + libc::getrandom( + buf.as_mut_ptr() as *mut libc::c_void, + 5 as libc::size_t, + 0 as libc::c_uint, + ), + 5, + ); + } +} diff --git a/src/tools/miri/tests/pass-dep/shims/posix_memalign.rs b/src/tools/miri/tests/pass-dep/shims/posix_memalign.rs new file mode 100644 index 0000000000000..9bd8a00d68dcd --- /dev/null +++ b/src/tools/miri/tests/pass-dep/shims/posix_memalign.rs @@ -0,0 +1,82 @@ +//@ignore-target-windows: No libc on Windows + +#![feature(pointer_is_aligned)] +#![feature(strict_provenance)] + +use core::ptr; + +fn main() { + // A normal allocation. + unsafe { + let mut ptr: *mut libc::c_void = ptr::null_mut(); + let align = 8; + let size = 64; + assert_eq!(libc::posix_memalign(&mut ptr, align, size), 0); + assert!(!ptr.is_null()); + assert!(ptr.is_aligned_to(align)); + ptr.cast::().write_bytes(1, size); + libc::free(ptr); + } + + // Align > size. + unsafe { + let mut ptr: *mut libc::c_void = ptr::null_mut(); + let align = 64; + let size = 8; + assert_eq!(libc::posix_memalign(&mut ptr, align, size), 0); + assert!(!ptr.is_null()); + assert!(ptr.is_aligned_to(align)); + ptr.cast::().write_bytes(1, size); + libc::free(ptr); + } + + // Size not multiple of align + unsafe { + let mut ptr: *mut libc::c_void = ptr::null_mut(); + let align = 16; + let size = 31; + assert_eq!(libc::posix_memalign(&mut ptr, align, size), 0); + assert!(!ptr.is_null()); + assert!(ptr.is_aligned_to(align)); + ptr.cast::().write_bytes(1, size); + libc::free(ptr); + } + + // Size == 0 + unsafe { + let mut ptr: *mut libc::c_void = ptr::null_mut(); + let align = 64; + let size = 0; + assert_eq!(libc::posix_memalign(&mut ptr, align, size), 0); + // We are not required to return null if size == 0, but we currently do. + // It's fine to remove this assert if we start returning non-null pointers. + assert!(ptr.is_null()); + assert!(ptr.is_aligned_to(align)); + // Regardless of what we return, it must be `free`able. + libc::free(ptr); + } + + // Non-power of 2 align + unsafe { + let mut ptr: *mut libc::c_void = ptr::invalid_mut(0x1234567); + let align = 15; + let size = 8; + assert_eq!(libc::posix_memalign(&mut ptr, align, size), libc::EINVAL); + // The pointer is not modified on failure, posix_memalign(3) says: + // > On Linux (and other systems), posix_memalign() does not modify memptr on failure. + // > A requirement standardizing this behavior was added in POSIX.1-2008 TC2. + assert_eq!(ptr.addr(), 0x1234567); + } + + // Too small align (smaller than ptr) + unsafe { + let mut ptr: *mut libc::c_void = ptr::invalid_mut(0x1234567); + let align = std::mem::size_of::() / 2; + let size = 8; + assert_eq!(libc::posix_memalign(&mut ptr, align, size), libc::EINVAL); + // The pointer is not modified on failure, posix_memalign(3) says: + // > On Linux (and other systems), posix_memalign() does not modify memptr on failure. + // > A requirement standardizing this behavior was added in POSIX.1-2008 TC2. + assert_eq!(ptr.addr(), 0x1234567); + } +} diff --git a/src/tools/miri/tests/pass-dep/shims/pthreads.rs b/src/tools/miri/tests/pass-dep/shims/pthreads.rs new file mode 100644 index 0000000000000..d062eda7e90c8 --- /dev/null +++ b/src/tools/miri/tests/pass-dep/shims/pthreads.rs @@ -0,0 +1,127 @@ +//@ignore-target-windows: No libc on Windows + +fn main() { + test_mutex_libc_init_recursive(); + test_mutex_libc_init_normal(); + test_mutex_libc_init_errorcheck(); + test_rwlock_libc_static_initializer(); + + #[cfg(any(target_os = "linux"))] + test_mutex_libc_static_initializer_recursive(); +} + +fn test_mutex_libc_init_recursive() { + unsafe { + let mut attr: libc::pthread_mutexattr_t = std::mem::zeroed(); + assert_eq!(libc::pthread_mutexattr_init(&mut attr as *mut _), 0); + assert_eq!( + libc::pthread_mutexattr_settype(&mut attr as *mut _, libc::PTHREAD_MUTEX_RECURSIVE), + 0, + ); + let mut mutex: libc::pthread_mutex_t = std::mem::zeroed(); + assert_eq!(libc::pthread_mutex_init(&mut mutex as *mut _, &mut attr as *mut _), 0); + assert_eq!(libc::pthread_mutex_lock(&mut mutex as *mut _), 0); + assert_eq!(libc::pthread_mutex_trylock(&mut mutex as *mut _), 0); + assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), 0); + assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), 0); + assert_eq!(libc::pthread_mutex_trylock(&mut mutex as *mut _), 0); + assert_eq!(libc::pthread_mutex_lock(&mut mutex as *mut _), 0); + assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), 0); + assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), 0); + assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), libc::EPERM); + assert_eq!(libc::pthread_mutex_destroy(&mut mutex as *mut _), 0); + assert_eq!(libc::pthread_mutexattr_destroy(&mut attr as *mut _), 0); + } +} + +fn test_mutex_libc_init_normal() { + unsafe { + let mut mutexattr: libc::pthread_mutexattr_t = std::mem::zeroed(); + assert_eq!( + libc::pthread_mutexattr_settype(&mut mutexattr as *mut _, 0x12345678), + libc::EINVAL, + ); + assert_eq!( + libc::pthread_mutexattr_settype(&mut mutexattr as *mut _, libc::PTHREAD_MUTEX_NORMAL), + 0, + ); + let mut mutex: libc::pthread_mutex_t = std::mem::zeroed(); + assert_eq!(libc::pthread_mutex_init(&mut mutex as *mut _, &mutexattr as *const _), 0); + assert_eq!(libc::pthread_mutex_lock(&mut mutex as *mut _), 0); + assert_eq!(libc::pthread_mutex_trylock(&mut mutex as *mut _), libc::EBUSY); + assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), 0); + assert_eq!(libc::pthread_mutex_trylock(&mut mutex as *mut _), 0); + assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), 0); + assert_eq!(libc::pthread_mutex_destroy(&mut mutex as *mut _), 0); + } +} + +fn test_mutex_libc_init_errorcheck() { + unsafe { + let mut mutexattr: libc::pthread_mutexattr_t = std::mem::zeroed(); + assert_eq!( + libc::pthread_mutexattr_settype( + &mut mutexattr as *mut _, + libc::PTHREAD_MUTEX_ERRORCHECK, + ), + 0, + ); + let mut mutex: libc::pthread_mutex_t = std::mem::zeroed(); + assert_eq!(libc::pthread_mutex_init(&mut mutex as *mut _, &mutexattr as *const _), 0); + assert_eq!(libc::pthread_mutex_lock(&mut mutex as *mut _), 0); + assert_eq!(libc::pthread_mutex_trylock(&mut mutex as *mut _), libc::EBUSY); + assert_eq!(libc::pthread_mutex_lock(&mut mutex as *mut _), libc::EDEADLK); + assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), 0); + assert_eq!(libc::pthread_mutex_trylock(&mut mutex as *mut _), 0); + assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), 0); + assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), libc::EPERM); + assert_eq!(libc::pthread_mutex_destroy(&mut mutex as *mut _), 0); + } +} + +// Only linux provides PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP, +// libc for macOS just has the default PTHREAD_MUTEX_INITIALIZER. +#[cfg(target_os = "linux")] +fn test_mutex_libc_static_initializer_recursive() { + let mutex = std::cell::UnsafeCell::new(libc::PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP); + unsafe { + assert_eq!(libc::pthread_mutex_lock(mutex.get()), 0); + assert_eq!(libc::pthread_mutex_trylock(mutex.get()), 0); + assert_eq!(libc::pthread_mutex_unlock(mutex.get()), 0); + assert_eq!(libc::pthread_mutex_unlock(mutex.get()), 0); + assert_eq!(libc::pthread_mutex_trylock(mutex.get()), 0); + assert_eq!(libc::pthread_mutex_lock(mutex.get()), 0); + assert_eq!(libc::pthread_mutex_unlock(mutex.get()), 0); + assert_eq!(libc::pthread_mutex_unlock(mutex.get()), 0); + assert_eq!(libc::pthread_mutex_unlock(mutex.get()), libc::EPERM); + assert_eq!(libc::pthread_mutex_destroy(mutex.get()), 0); + } +} + +// Testing the behavior of std::sync::RwLock does not fully exercise the pthread rwlock shims, we +// need to go a layer deeper and test the behavior of the libc functions, because +// std::sys::unix::rwlock::RWLock itself keeps track of write_locked and num_readers. +fn test_rwlock_libc_static_initializer() { + let rw = std::cell::UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER); + unsafe { + assert_eq!(libc::pthread_rwlock_rdlock(rw.get()), 0); + assert_eq!(libc::pthread_rwlock_rdlock(rw.get()), 0); + assert_eq!(libc::pthread_rwlock_unlock(rw.get()), 0); + assert_eq!(libc::pthread_rwlock_tryrdlock(rw.get()), 0); + assert_eq!(libc::pthread_rwlock_unlock(rw.get()), 0); + assert_eq!(libc::pthread_rwlock_trywrlock(rw.get()), libc::EBUSY); + assert_eq!(libc::pthread_rwlock_unlock(rw.get()), 0); + + assert_eq!(libc::pthread_rwlock_wrlock(rw.get()), 0); + assert_eq!(libc::pthread_rwlock_tryrdlock(rw.get()), libc::EBUSY); + assert_eq!(libc::pthread_rwlock_trywrlock(rw.get()), libc::EBUSY); + assert_eq!(libc::pthread_rwlock_unlock(rw.get()), 0); + + assert_eq!(libc::pthread_rwlock_trywrlock(rw.get()), 0); + assert_eq!(libc::pthread_rwlock_tryrdlock(rw.get()), libc::EBUSY); + assert_eq!(libc::pthread_rwlock_trywrlock(rw.get()), libc::EBUSY); + assert_eq!(libc::pthread_rwlock_unlock(rw.get()), 0); + + assert_eq!(libc::pthread_rwlock_destroy(rw.get()), 0); + } +} diff --git a/src/tools/miri/tests/pass/0weak_memory_consistency.rs b/src/tools/miri/tests/pass/0weak_memory_consistency.rs new file mode 100644 index 0000000000000..f3820bd660d28 --- /dev/null +++ b/src/tools/miri/tests/pass/0weak_memory_consistency.rs @@ -0,0 +1,301 @@ +//@compile-flags: -Zmiri-ignore-leaks -Zmiri-disable-stacked-borrows + +// The following tests check whether our weak memory emulation produces +// any inconsistent execution outcomes +// +// Due to the random nature of choosing valid stores, it is always +// possible that our tests spuriously succeeds: even though our weak +// memory emulation code has incorrectly identified a store in +// modification order as being valid, it may be never chosen by +// the RNG and never observed in our tests. +// +// To mitigate this, each test is ran enough times such that the chance +// of spurious success is very low. These tests never supriously fail. + +// Test cases and their consistent outcomes are from +// http://svr-pes20-cppmem.cl.cam.ac.uk/cppmem/ +// Based on +// M. Batty, S. Owens, S. Sarkar, P. Sewell and T. Weber, +// "Mathematizing C++ concurrency", ACM SIGPLAN Notices, vol. 46, no. 1, pp. 55-66, 2011. +// Available: https://ss265.host.cs.st-andrews.ac.uk/papers/n3132.pdf. + +use std::sync::atomic::Ordering::*; +use std::sync::atomic::{fence, AtomicBool, AtomicI32}; +use std::thread::spawn; + +#[derive(Copy, Clone)] +struct EvilSend(pub T); + +unsafe impl Send for EvilSend {} +unsafe impl Sync for EvilSend {} + +// We can't create static items because we need to run each test +// multiple times +fn static_atomic(val: i32) -> &'static AtomicI32 { + let ret = Box::leak(Box::new(AtomicI32::new(val))); + ret.store(val, Relaxed); // work around https://github.com/rust-lang/miri/issues/2164 + ret +} +fn static_atomic_bool(val: bool) -> &'static AtomicBool { + let ret = Box::leak(Box::new(AtomicBool::new(val))); + ret.store(val, Relaxed); // work around https://github.com/rust-lang/miri/issues/2164 + ret +} + +// Spins until it acquires a pre-determined value. +fn acquires_value(loc: &AtomicI32, val: i32) -> i32 { + while loc.load(Acquire) != val { + std::hint::spin_loop(); + } + val +} + +fn test_corr() { + let x = static_atomic(0); + let y = static_atomic(0); + + let j1 = spawn(move || { + x.store(1, Relaxed); + x.store(2, Relaxed); + }); + + #[rustfmt::skip] + let j2 = spawn(move || { + let r2 = x.load(Relaxed); // -------------------------------------+ + y.store(1, Release); // ---------------------+ | + r2 // | | + }); // | | + #[rustfmt::skip] // |synchronizes-with |happens-before + let j3 = spawn(move || { // | | + acquires_value(&y, 1); // <------------------+ | + x.load(Relaxed) // <----------------------------------------------+ + // The two reads on x are ordered by hb, so they cannot observe values + // differently from the modification order. If the first read observed + // 2, then the second read must observe 2 as well. + }); + + j1.join().unwrap(); + let r2 = j2.join().unwrap(); + let r3 = j3.join().unwrap(); + if r2 == 2 { + assert_eq!(r3, 2); + } +} + +fn test_wrc() { + let x = static_atomic(0); + let y = static_atomic(0); + + #[rustfmt::skip] + let j1 = spawn(move || { + x.store(1, Release); // ---------------------+---------------------+ + }); // | | + #[rustfmt::skip] // |synchronizes-with | + let j2 = spawn(move || { // | | + acquires_value(&x, 1); // <------------------+ | + y.store(1, Release); // ---------------------+ |happens-before + }); // | | + #[rustfmt::skip] // |synchronizes-with | + let j3 = spawn(move || { // | | + acquires_value(&y, 1); // <------------------+ | + x.load(Relaxed) // <-----------------------------------------------+ + }); + + j1.join().unwrap(); + j2.join().unwrap(); + let r3 = j3.join().unwrap(); + + assert_eq!(r3, 1); +} + +fn test_message_passing() { + let mut var = 0u32; + let ptr = &mut var as *mut u32; + let x = EvilSend(ptr); + let y = static_atomic(0); + + #[rustfmt::skip] + let j1 = spawn(move || { + unsafe { *x.0 = 1 }; // -----------------------------------------+ + y.store(1, Release); // ---------------------+ | + }); // | | + #[rustfmt::skip] // |synchronizes-with | happens-before + let j2 = spawn(move || { // | | + acquires_value(&y, 1); // <------------------+ | + unsafe { *x.0 } // <---------------------------------------------+ + }); + + j1.join().unwrap(); + let r2 = j2.join().unwrap(); + + assert_eq!(r2, 1); +} + +// LB+acq_rel+acq_rel +fn test_load_buffering_acq_rel() { + let x = static_atomic(0); + let y = static_atomic(0); + let j1 = spawn(move || { + let r1 = x.load(Acquire); + y.store(1, Release); + r1 + }); + + let j2 = spawn(move || { + let r2 = y.load(Acquire); + x.store(1, Release); + r2 + }); + + let r1 = j1.join().unwrap(); + let r2 = j2.join().unwrap(); + + // 3 consistent outcomes: (0,0), (0,1), (1,0) + assert_ne!((r1, r2), (1, 1)); +} + +fn test_mixed_access() { + /* + int main() { + atomic_int x = 0; + {{{ + x.store(1, mo_relaxed); + }}} + + x.store(2, mo_relaxed); + + {{{ + r1 = x.load(mo_relaxed); + }}} + + return 0; + } + */ + let x = static_atomic(0); + + spawn(move || { + x.store(1, Relaxed); + }) + .join() + .unwrap(); + + x.store(2, Relaxed); + + let r2 = spawn(move || x.load(Relaxed)).join().unwrap(); + + assert_eq!(r2, 2); +} + +// The following two tests are taken from Repairing Sequential Consistency in C/C++11 +// by Lahav et al. +// https://plv.mpi-sws.org/scfix/paper.pdf + +// Test case SB +fn test_sc_store_buffering() { + let x = static_atomic(0); + let y = static_atomic(0); + + let j1 = spawn(move || { + x.store(1, SeqCst); + y.load(SeqCst) + }); + + let j2 = spawn(move || { + y.store(1, SeqCst); + x.load(SeqCst) + }); + + let a = j1.join().unwrap(); + let b = j2.join().unwrap(); + + assert_ne!((a, b), (0, 0)); +} + +fn test_single_thread() { + let x = AtomicI32::new(42); + + assert_eq!(x.load(Relaxed), 42); + + x.store(43, Relaxed); + + assert_eq!(x.load(Relaxed), 43); +} + +fn test_sync_through_rmw_and_fences() { + // Example from https://github.com/llvm/llvm-project/issues/56450#issuecomment-1183695905 + #[no_mangle] + pub fn rdmw(storing: &AtomicI32, sync: &AtomicI32, loading: &AtomicI32) -> i32 { + storing.store(1, Relaxed); + fence(Release); + sync.fetch_add(0, Relaxed); + fence(Acquire); + loading.load(Relaxed) + } + + let x = static_atomic(0); + let y = static_atomic(0); + let z = static_atomic(0); + + // Since each thread is so short, we need to make sure that they truely run at the same time + // Otherwise t1 will finish before t2 even starts + let go = static_atomic_bool(false); + + let t1 = spawn(move || { + while !go.load(Relaxed) {} + rdmw(y, x, z) + }); + + let t2 = spawn(move || { + while !go.load(Relaxed) {} + rdmw(z, x, y) + }); + + go.store(true, Relaxed); + + let a = t1.join().unwrap(); + let b = t2.join().unwrap(); + assert_ne!((a, b), (0, 0)); +} + +// Test case by @SabrinaJewson +// https://github.com/rust-lang/miri/issues/2301#issuecomment-1221502757 +// Demonstrating C++20 SC access changes +fn test_iriw_sc_rlx() { + let x = static_atomic_bool(false); + let y = static_atomic_bool(false); + + x.store(false, Relaxed); + y.store(false, Relaxed); + + let a = spawn(move || x.store(true, Relaxed)); + let b = spawn(move || y.store(true, Relaxed)); + let c = spawn(move || { + while !x.load(SeqCst) {} + y.load(SeqCst) + }); + let d = spawn(move || { + while !y.load(SeqCst) {} + x.load(SeqCst) + }); + + a.join().unwrap(); + b.join().unwrap(); + let c = c.join().unwrap(); + let d = d.join().unwrap(); + + assert!(c || d); +} + +pub fn main() { + for _ in 0..50 { + test_single_thread(); + test_mixed_access(); + test_load_buffering_acq_rel(); + test_message_passing(); + test_wrc(); + test_corr(); + test_sc_store_buffering(); + test_sync_through_rmw_and_fences(); + test_iriw_sc_rlx(); + } +} diff --git a/src/tools/miri/tests/pass/adjacent-allocs.rs b/src/tools/miri/tests/pass/adjacent-allocs.rs new file mode 100644 index 0000000000000..0051c62121cd5 --- /dev/null +++ b/src/tools/miri/tests/pass/adjacent-allocs.rs @@ -0,0 +1,63 @@ +//@compile-flags: -Zmiri-permissive-provenance + +fn ensure_allocs_can_be_adjacent() { + for _ in 0..512 { + let n = 0u64; + let ptr: *const u64 = &n; + let ptr2 = { + let m = 0u64; + &m as *const u64 + }; + if ptr.wrapping_add(1) == ptr2 { + return; + } + } + panic!("never saw adjacent stack variables?"); +} + +fn test1() { + // The slack between allocations is random. + // Loop a few times to hit the zero-slack case. + for _ in 0..512 { + let n = 0u64; + let ptr: *const u64 = &n; + + // Allocate a new stack variable whose lifetime quickly ends. + // If there's a chance that &m == ptr.add(1), then an int-to-ptr cast of + // that value will have ambiguous provenance between n and m. + // See https://github.com/rust-lang/miri/issues/1866#issuecomment-985770125 + { + let m = 0u64; + let _ = &m as *const u64; + } + + let iptr = ptr as usize; + let zst = (iptr + 8) as *const (); + // This is a ZST ptr just at the end of `n`, so it should be valid to deref. + unsafe { *zst } + } +} + +fn test2() { + fn foo() -> u64 { + 0 + } + + for _ in 0..512 { + let n = 0u64; + let ptr: *const u64 = &n; + foo(); + let iptr = ptr as usize; + unsafe { + let start = &*std::ptr::slice_from_raw_parts(iptr as *const (), 1); + let end = &*std::ptr::slice_from_raw_parts((iptr + 8) as *const (), 1); + assert_eq!(start.len(), end.len()); + } + } +} + +fn main() { + ensure_allocs_can_be_adjacent(); + test1(); + test2(); +} diff --git a/src/tools/miri/tests/pass/align.rs b/src/tools/miri/tests/pass/align.rs new file mode 100644 index 0000000000000..997abd7339226 --- /dev/null +++ b/src/tools/miri/tests/pass/align.rs @@ -0,0 +1,29 @@ +//@compile-flags: -Zmiri-permissive-provenance + +/// This manually makes sure that we have a pointer with the proper alignment. +fn manual_alignment() { + let x = &mut [0u8; 3]; + let base_addr = x as *mut _ as usize; + let base_addr_aligned = if base_addr % 2 == 0 { base_addr } else { base_addr + 1 }; + let u16_ptr = base_addr_aligned as *mut u16; + unsafe { + *u16_ptr = 2; + } +} + +/// Test standard library `align_to`. +fn align_to() { + const LEN: usize = 128; + let buf = &[0u8; LEN]; + let (l, m, r) = unsafe { buf.align_to::() }; + assert!(m.len() * 4 >= LEN - 4); + assert!(l.len() + r.len() <= 4); +} + +fn main() { + // Do this a couple times in a loop because it may work "by chance". + for _ in 0..10 { + manual_alignment(); + align_to(); + } +} diff --git a/src/tools/miri/tests/pass/align_offset_symbolic.rs b/src/tools/miri/tests/pass/align_offset_symbolic.rs new file mode 100644 index 0000000000000..b3e5733836390 --- /dev/null +++ b/src/tools/miri/tests/pass/align_offset_symbolic.rs @@ -0,0 +1,105 @@ +//@compile-flags: -Zmiri-symbolic-alignment-check + +fn test_align_offset() { + let d = Box::new([0u32; 4]); + // Get u8 pointer to base + let raw = d.as_ptr() as *const u8; + + assert_eq!(raw.align_offset(2), 0); + assert_eq!(raw.align_offset(4), 0); + assert_eq!(raw.align_offset(8), usize::MAX); // requested alignment higher than allocation alignment + + assert_eq!(raw.wrapping_offset(1).align_offset(2), 1); + assert_eq!(raw.wrapping_offset(1).align_offset(4), 3); + assert_eq!(raw.wrapping_offset(1).align_offset(8), usize::MAX); // requested alignment higher than allocation alignment + + assert_eq!(raw.wrapping_offset(2).align_offset(2), 0); + assert_eq!(raw.wrapping_offset(2).align_offset(4), 2); + assert_eq!(raw.wrapping_offset(2).align_offset(8), usize::MAX); // requested alignment higher than allocation alignment +} + +fn test_align_to() { + const N: usize = 4; + let d = Box::new([0u32; N]); + // Get u8 slice covering the entire thing + let s = unsafe { std::slice::from_raw_parts(d.as_ptr() as *const u8, 4 * N) }; + let raw = s.as_ptr(); + + { + let (l, m, r) = unsafe { s.align_to::() }; + assert_eq!(l.len(), 0); + assert_eq!(r.len(), 0); + assert_eq!(m.len(), N); + assert_eq!(raw, m.as_ptr() as *const u8); + } + + { + let (l, m, r) = unsafe { s[1..].align_to::() }; + assert_eq!(l.len(), 3); + assert_eq!(m.len(), N - 1); + assert_eq!(r.len(), 0); + assert_eq!(raw.wrapping_offset(4), m.as_ptr() as *const u8); + } + + { + let (l, m, r) = unsafe { s[..4 * N - 1].align_to::() }; + assert_eq!(l.len(), 0); + assert_eq!(m.len(), N - 1); + assert_eq!(r.len(), 3); + assert_eq!(raw, m.as_ptr() as *const u8); + } + + { + let (l, m, r) = unsafe { s[1..4 * N - 1].align_to::() }; + assert_eq!(l.len(), 3); + assert_eq!(m.len(), N - 2); + assert_eq!(r.len(), 3); + assert_eq!(raw.wrapping_offset(4), m.as_ptr() as *const u8); + } + + { + #[repr(align(8))] + struct Align8(u64); + + let (l, m, r) = unsafe { s.align_to::() }; // requested alignment higher than allocation alignment + assert_eq!(l.len(), 4 * N); + assert_eq!(r.len(), 0); + assert_eq!(m.len(), 0); + } +} + +fn test_from_utf8() { + const N: usize = 10; + let vec = vec![0x4141414141414141u64; N]; + let content = unsafe { std::slice::from_raw_parts(vec.as_ptr() as *const u8, 8 * N) }; + println!("{:?}", std::str::from_utf8(content).unwrap()); +} + +fn test_u64_array() { + #[derive(Default)] + #[repr(align(8))] + struct AlignToU64(T); + + const BYTE_LEN: usize = std::mem::size_of::<[u64; 4]>(); + type Data = AlignToU64<[u8; BYTE_LEN]>; + + fn example(data: &Data) { + let (head, u64_arrays, tail) = unsafe { data.0.align_to::<[u64; 4]>() }; + + assert!(head.is_empty(), "buffer was not aligned for 64-bit numbers"); + assert_eq!(u64_arrays.len(), 1, "buffer was not long enough"); + assert!(tail.is_empty(), "buffer was too long"); + + let u64_array = &u64_arrays[0]; + let _val = u64_array[0]; // make sure we can actually load this + } + + example(&Data::default()); +} + +fn main() { + test_align_offset(); + test_align_to(); + test_from_utf8(); + test_u64_array(); +} diff --git a/src/tools/miri/tests/pass/align_offset_symbolic.stdout b/src/tools/miri/tests/pass/align_offset_symbolic.stdout new file mode 100644 index 0000000000000..66d4399481596 --- /dev/null +++ b/src/tools/miri/tests/pass/align_offset_symbolic.stdout @@ -0,0 +1 @@ +"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" diff --git a/src/tools/miri/tests/pass/arrays.rs b/src/tools/miri/tests/pass/arrays.rs new file mode 100644 index 0000000000000..9589ffa1724c2 --- /dev/null +++ b/src/tools/miri/tests/pass/arrays.rs @@ -0,0 +1,78 @@ +use std::convert::TryFrom; + +fn empty_array() -> [u16; 0] { + [] +} + +fn mini_array() -> [u16; 1] { + [42] +} + +fn big_array() -> [u16; 5] { + [5, 4, 3, 2, 1] +} + +fn array_array() -> [[u8; 2]; 3] { + [[5, 4], [3, 2], [1, 0]] +} + +fn index_unsafe() -> i32 { + let a = [0, 10, 20, 30]; + unsafe { *a.get_unchecked(2) } +} + +fn index() -> i32 { + let a = [0, 10, 20, 30]; + a[2] +} + +fn array_repeat() -> [u8; 8] { + [42; 8] +} + +fn slice_index() -> u8 { + let arr: &[_] = &[101, 102, 103, 104, 105, 106]; + arr[5] +} + +fn from() { + const N: usize = 16; + type Array = [u8; N]; + let array: Array = [0; N]; + let slice: &[u8] = &array[..]; + + let result = <&Array>::try_from(slice); + assert_eq!(&array, result.unwrap()); + + let vec = Vec::from(array); + assert_eq!(vec.len(), N); +} + +fn eq() { + const N: usize = 16; + type Array = [u8; N]; + let array1: Array = [0; N]; + let array2: Array = [0; N]; + let array3: Array = [1; N]; + assert_eq!(array1, array2); + assert_ne!(array1, array3); +} + +fn debug() { + let array = [0u8, 42, 13, 71]; + println!("{:?}", array); +} + +fn main() { + assert_eq!(empty_array(), []); + assert_eq!(index_unsafe(), 20); + assert_eq!(index(), 20); + assert_eq!(slice_index(), 106); + assert_eq!(big_array(), [5, 4, 3, 2, 1]); + assert_eq!(array_array(), [[5, 4], [3, 2], [1, 0]]); + assert_eq!(array_repeat(), [42; 8]); + assert_eq!(mini_array(), [42]); + from(); + eq(); + debug(); +} diff --git a/src/tools/miri/tests/pass/arrays.stdout b/src/tools/miri/tests/pass/arrays.stdout new file mode 100644 index 0000000000000..6c3cefadf8035 --- /dev/null +++ b/src/tools/miri/tests/pass/arrays.stdout @@ -0,0 +1 @@ +[0, 42, 13, 71] diff --git a/src/tools/miri/tests/pass/associated-const.rs b/src/tools/miri/tests/pass/associated-const.rs new file mode 100644 index 0000000000000..2ff08ffc4bf6a --- /dev/null +++ b/src/tools/miri/tests/pass/associated-const.rs @@ -0,0 +1,11 @@ +trait Foo { + const ID: i32; +} + +impl Foo for i32 { + const ID: i32 = 1; +} + +fn main() { + assert_eq!(1, ::ID); +} diff --git a/src/tools/miri/tests/pass/assume_bug.rs b/src/tools/miri/tests/pass/assume_bug.rs new file mode 100644 index 0000000000000..e14f875c022e3 --- /dev/null +++ b/src/tools/miri/tests/pass/assume_bug.rs @@ -0,0 +1,3 @@ +fn main() { + vec![()].into_iter(); +} diff --git a/src/tools/miri/tests/pass/async-fn.rs b/src/tools/miri/tests/pass/async-fn.rs new file mode 100644 index 0000000000000..1d5d9a27cc8a5 --- /dev/null +++ b/src/tools/miri/tests/pass/async-fn.rs @@ -0,0 +1,90 @@ +#![feature(never_type)] + +use std::future::Future; + +// See if we can run a basic `async fn` +pub async fn foo(x: &u32, y: u32) -> u32 { + let y = &y; + let z = 9; + let z = &z; + let y = async { *y + *z }.await; + let a = 10; + let a = &a; + *x + y + *a +} + +async fn add(x: u32, y: u32) -> u32 { + let a = async { x + y }; + a.await +} + +async fn build_aggregate(a: u32, b: u32, c: u32, d: u32) -> u32 { + let x = (add(a, b).await, add(c, d).await); + x.0 + x.1 +} + +enum Never {} +fn never() -> Never { + panic!() +} + +async fn includes_never(crash: bool, x: u32) -> u32 { + let result = async { x * x }.await; + if !crash { + return result; + } + #[allow(unused)] + let bad = never(); + result *= async { x + x }.await; + drop(bad); + result +} + +async fn partial_init(x: u32) -> u32 { + #[allow(unreachable_code)] + let _x: (String, !) = (String::new(), return async { x + x }.await); +} + +async fn read_exact(_from: &mut &[u8], _to: &mut [u8]) -> Option<()> { + Some(()) +} + +async fn hello_world() { + let data = [0u8; 1]; + let mut reader = &data[..]; + + let mut marker = [0u8; 1]; + read_exact(&mut reader, &mut marker).await.unwrap(); +} + +fn run_fut(fut: impl Future) -> T { + use std::sync::Arc; + use std::task::{Context, Poll, Wake, Waker}; + + struct MyWaker; + impl Wake for MyWaker { + fn wake(self: Arc) { + unimplemented!() + } + } + + let waker = Waker::from(Arc::new(MyWaker)); + let mut context = Context::from_waker(&waker); + + let mut pinned = Box::pin(fut); + loop { + match pinned.as_mut().poll(&mut context) { + Poll::Pending => continue, + Poll::Ready(v) => return v, + } + } +} + +fn main() { + let x = 5; + assert_eq!(run_fut(foo(&x, 7)), 31); + assert_eq!(run_fut(build_aggregate(1, 2, 3, 4)), 10); + assert_eq!(run_fut(includes_never(false, 4)), 16); + assert_eq!(run_fut(partial_init(4)), 8); + run_fut(hello_world()); +} diff --git a/src/tools/miri/tests/pass/atomic-compare-exchange-weak-never-fail.rs b/src/tools/miri/tests/pass/atomic-compare-exchange-weak-never-fail.rs new file mode 100644 index 0000000000000..8d3d71869f42e --- /dev/null +++ b/src/tools/miri/tests/pass/atomic-compare-exchange-weak-never-fail.rs @@ -0,0 +1,17 @@ +//@compile-flags: -Zmiri-compare-exchange-weak-failure-rate=0.0 +use std::sync::atomic::{AtomicBool, Ordering::*}; + +// Ensure that compare_exchange_weak never fails. +fn main() { + let atomic = AtomicBool::new(false); + let tries = 100; + for _ in 0..tries { + let cur = atomic.load(Relaxed); + // Try (weakly) to flip the flag. + if atomic.compare_exchange_weak(cur, !cur, Relaxed, Relaxed).is_err() { + // We failed. Avoid panic machinery as that uses atomics/locks. + eprintln!("compare_exchange_weak failed"); + std::process::abort(); + } + } +} diff --git a/src/tools/miri/tests/pass/atomic.rs b/src/tools/miri/tests/pass/atomic.rs new file mode 100644 index 0000000000000..e3d80a78916f6 --- /dev/null +++ b/src/tools/miri/tests/pass/atomic.rs @@ -0,0 +1,191 @@ +//@compile-flags: -Zmiri-strict-provenance +#![feature(strict_provenance, strict_provenance_atomic_ptr)] +use std::sync::atomic::{ + compiler_fence, fence, AtomicBool, AtomicIsize, AtomicPtr, AtomicU64, Ordering::*, +}; + +fn main() { + atomic_bool(); + atomic_all_ops(); + atomic_u64(); + atomic_fences(); + atomic_ptr(); + weak_sometimes_fails(); +} + +fn atomic_bool() { + static mut ATOMIC: AtomicBool = AtomicBool::new(false); + + unsafe { + assert_eq!(*ATOMIC.get_mut(), false); + ATOMIC.store(true, SeqCst); + assert_eq!(*ATOMIC.get_mut(), true); + ATOMIC.fetch_or(false, SeqCst); + assert_eq!(*ATOMIC.get_mut(), true); + ATOMIC.fetch_and(false, SeqCst); + assert_eq!(*ATOMIC.get_mut(), false); + ATOMIC.fetch_nand(true, SeqCst); + assert_eq!(*ATOMIC.get_mut(), true); + ATOMIC.fetch_xor(true, SeqCst); + assert_eq!(*ATOMIC.get_mut(), false); + } +} + +// There isn't a trait to use to make this generic, so just use a macro +macro_rules! compare_exchange_weak_loop { + ($atom:expr, $from:expr, $to:expr, $succ_order:expr, $fail_order:expr) => { + loop { + match $atom.compare_exchange_weak($from, $to, $succ_order, $fail_order) { + Ok(n) => { + assert_eq!(n, $from); + break; + } + Err(n) => assert_eq!(n, $from), + } + } + }; +} + +/// Make sure we can handle all the intrinsics +fn atomic_all_ops() { + static ATOMIC: AtomicIsize = AtomicIsize::new(0); + static ATOMIC_UNSIGNED: AtomicU64 = AtomicU64::new(0); + + let load_orders = [Relaxed, Acquire, SeqCst]; + let stored_orders = [Relaxed, Release, SeqCst]; + let rmw_orders = [Relaxed, Release, Acquire, AcqRel, SeqCst]; + + // loads + for o in load_orders { + ATOMIC.load(o); + } + + // stores + for o in stored_orders { + ATOMIC.store(1, o); + } + + // most RMWs + for o in rmw_orders { + ATOMIC.swap(0, o); + ATOMIC.fetch_or(0, o); + ATOMIC.fetch_xor(0, o); + ATOMIC.fetch_and(0, o); + ATOMIC.fetch_nand(0, o); + ATOMIC.fetch_add(0, o); + ATOMIC.fetch_sub(0, o); + ATOMIC.fetch_min(0, o); + ATOMIC.fetch_max(0, o); + ATOMIC_UNSIGNED.fetch_min(0, o); + ATOMIC_UNSIGNED.fetch_max(0, o); + } + + // RMWs with separate failure ordering + for o1 in rmw_orders { + for o2 in load_orders { + let _res = ATOMIC.compare_exchange(0, 0, o1, o2); + let _res = ATOMIC.compare_exchange_weak(0, 0, o1, o2); + } + } +} + +fn atomic_u64() { + static ATOMIC: AtomicU64 = AtomicU64::new(0); + + ATOMIC.store(1, SeqCst); + assert_eq!(ATOMIC.compare_exchange(0, 0x100, AcqRel, Acquire), Err(1)); + assert_eq!(ATOMIC.compare_exchange(0, 1, Release, Relaxed), Err(1)); + assert_eq!(ATOMIC.compare_exchange(1, 0, AcqRel, Relaxed), Ok(1)); + assert_eq!(ATOMIC.compare_exchange(0, 1, Relaxed, Relaxed), Ok(0)); + compare_exchange_weak_loop!(ATOMIC, 1, 0x100, AcqRel, Acquire); + assert_eq!(ATOMIC.compare_exchange_weak(0, 2, Acquire, Relaxed), Err(0x100)); + assert_eq!(ATOMIC.compare_exchange_weak(0, 1, Release, Relaxed), Err(0x100)); + assert_eq!(ATOMIC.load(Relaxed), 0x100); + + assert_eq!(ATOMIC.fetch_max(0x10, SeqCst), 0x100); + assert_eq!(ATOMIC.fetch_max(0x100, SeqCst), 0x100); + assert_eq!(ATOMIC.fetch_max(0x1000, SeqCst), 0x100); + assert_eq!(ATOMIC.fetch_max(0x1000, SeqCst), 0x1000); + assert_eq!(ATOMIC.fetch_max(0x2000, SeqCst), 0x1000); + assert_eq!(ATOMIC.fetch_max(0x2000, SeqCst), 0x2000); + + assert_eq!(ATOMIC.fetch_min(0x2000, SeqCst), 0x2000); + assert_eq!(ATOMIC.fetch_min(0x2000, SeqCst), 0x2000); + assert_eq!(ATOMIC.fetch_min(0x1000, SeqCst), 0x2000); + assert_eq!(ATOMIC.fetch_min(0x1000, SeqCst), 0x1000); + assert_eq!(ATOMIC.fetch_min(0x100, SeqCst), 0x1000); + assert_eq!(ATOMIC.fetch_min(0x10, SeqCst), 0x100); +} + +fn atomic_fences() { + fence(SeqCst); + fence(Release); + fence(Acquire); + fence(AcqRel); + compiler_fence(SeqCst); + compiler_fence(Release); + compiler_fence(Acquire); + compiler_fence(AcqRel); +} + +fn atomic_ptr() { + use std::ptr; + let array: Vec = (0..100).into_iter().collect(); // a target to point to, to test provenance things + let x = array.as_ptr() as *mut i32; + + let ptr = AtomicPtr::::new(ptr::null_mut()); + assert!(ptr.load(Relaxed).addr() == 0); + ptr.store(ptr::invalid_mut(13), SeqCst); + assert!(ptr.swap(x, Relaxed).addr() == 13); + unsafe { assert!(*ptr.load(Acquire) == 0) }; + + // comparison ignores provenance + assert_eq!( + ptr.compare_exchange( + (&mut 0 as *mut i32).with_addr(x.addr()), + ptr::invalid_mut(0), + SeqCst, + SeqCst + ) + .unwrap() + .addr(), + x.addr(), + ); + assert_eq!( + ptr.compare_exchange( + (&mut 0 as *mut i32).with_addr(x.addr()), + ptr::invalid_mut(0), + SeqCst, + SeqCst + ) + .unwrap_err() + .addr(), + 0, + ); + ptr.store(x, Relaxed); + + assert_eq!(ptr.fetch_ptr_add(13, AcqRel).addr(), x.addr()); + unsafe { assert_eq!(*ptr.load(SeqCst), 13) }; // points to index 13 now + assert_eq!(ptr.fetch_ptr_sub(4, AcqRel).addr(), x.addr() + 13 * 4); + unsafe { assert_eq!(*ptr.load(SeqCst), 9) }; + assert_eq!(ptr.fetch_or(3, AcqRel).addr(), x.addr() + 9 * 4); // ptr is 4-aligned, so set the last 2 bits + assert_eq!(ptr.fetch_and(!3, AcqRel).addr(), (x.addr() + 9 * 4) | 3); // and unset them again + unsafe { assert_eq!(*ptr.load(SeqCst), 9) }; + assert_eq!(ptr.fetch_xor(0xdeadbeef, AcqRel).addr(), x.addr() + 9 * 4); + assert_eq!(ptr.fetch_xor(0xdeadbeef, AcqRel).addr(), (x.addr() + 9 * 4) ^ 0xdeadbeef); + unsafe { assert_eq!(*ptr.load(SeqCst), 9) }; // after XORing twice with the same thing, we get our ptr back +} + +fn weak_sometimes_fails() { + let atomic = AtomicBool::new(false); + let tries = 100; + for _ in 0..tries { + let cur = atomic.load(Relaxed); + // Try (weakly) to flip the flag. + if atomic.compare_exchange_weak(cur, !cur, Relaxed, Relaxed).is_err() { + // We failed, so return and skip the panic. + return; + } + } + panic!("compare_exchange_weak succeeded {} tries in a row", tries); +} diff --git a/src/tools/miri/tests/pass/available-parallelism.rs b/src/tools/miri/tests/pass/available-parallelism.rs new file mode 100644 index 0000000000000..eb4d09b1f5495 --- /dev/null +++ b/src/tools/miri/tests/pass/available-parallelism.rs @@ -0,0 +1,3 @@ +fn main() { + assert_eq!(std::thread::available_parallelism().unwrap().get(), 1); +} diff --git a/src/tools/miri/tests/pass/backtrace/backtrace-api-v0.rs b/src/tools/miri/tests/pass/backtrace/backtrace-api-v0.rs new file mode 100644 index 0000000000000..5cd12959ca40e --- /dev/null +++ b/src/tools/miri/tests/pass/backtrace/backtrace-api-v0.rs @@ -0,0 +1,67 @@ +//@normalize-stderr-test: "::<.*>" -> "" + +#[inline(never)] +fn func_a() -> Box<[*mut ()]> { + func_b::() +} +#[inline(never)] +fn func_b() -> Box<[*mut ()]> { + func_c() +} + +macro_rules! invoke_func_d { + () => { + func_d() + }; +} + +#[inline(never)] +fn func_c() -> Box<[*mut ()]> { + invoke_func_d!() +} +#[inline(never)] +fn func_d() -> Box<[*mut ()]> { + unsafe { miri_get_backtrace(0) } +} + +fn main() { + let mut seen_main = false; + let frames = func_a(); + for frame in frames.into_iter() { + let miri_frame = unsafe { miri_resolve_frame(*frame, 0) }; + let name = String::from_utf8(miri_frame.name.into()).unwrap(); + let filename = String::from_utf8(miri_frame.filename.into()).unwrap(); + + if name == "func_a" { + assert_eq!(func_a as *mut (), miri_frame.fn_ptr); + } + + // Print every frame to stderr. + let out = format!("{}:{}:{} ({})", filename, miri_frame.lineno, miri_frame.colno, name); + eprintln!("{}", out); + // Print the 'main' frame (and everything before it) to stdout, skipping + // the printing of internal (and possibly fragile) libstd frames. + if !seen_main { + println!("{}", out); + seen_main = name == "main"; + } + } +} + +// This goes at the bottom of the file so that we can change it +// without disturbing line numbers of the functions in the backtrace. + +extern "Rust" { + fn miri_get_backtrace(flags: u64) -> Box<[*mut ()]>; + fn miri_resolve_frame(ptr: *mut (), flags: u64) -> MiriFrame; +} + +#[derive(Debug)] +#[repr(C)] +struct MiriFrame { + name: Box<[u8]>, + filename: Box<[u8]>, + lineno: u32, + colno: u32, + fn_ptr: *mut (), +} diff --git a/src/tools/miri/tests/pass/backtrace/backtrace-api-v0.stderr b/src/tools/miri/tests/pass/backtrace/backtrace-api-v0.stderr new file mode 100644 index 0000000000000..ee556b3e4a05a --- /dev/null +++ b/src/tools/miri/tests/pass/backtrace/backtrace-api-v0.stderr @@ -0,0 +1,5 @@ +$DIR/backtrace-api-v0.rs:LL:CC (func_d) +$DIR/backtrace-api-v0.rs:LL:CC (func_c) +$DIR/backtrace-api-v0.rs:LL:CC (func_b) +$DIR/backtrace-api-v0.rs:LL:CC (func_a) +$DIR/backtrace-api-v0.rs:LL:CC RUSTLIB/core/src/ops/function.rs:LL:CC (>::call_once - RUSTLIB/std/src/sys_common/backtrace.rs:LL:CC (std::sys_common::backtrace::__rust_begin_short_backtrace) diff --git a/src/tools/miri/tests/pass/backtrace/backtrace-api-v0.stdout b/src/tools/miri/tests/pass/backtrace/backtrace-api-v0.stdout new file mode 100644 index 0000000000000..2fe31dd0e6bae --- /dev/null +++ b/src/tools/miri/tests/pass/backtrace/backtrace-api-v0.stdout @@ -0,0 +1,5 @@ +$DIR/backtrace-api-v0.rs:24:14 (func_d) +$DIR/backtrace-api-v0.rs:20:5 (func_c) +$DIR/backtrace-api-v0.rs:9:5 (func_b) +$DIR/backtrace-api-v0.rs:5:5 (func_a) +$DIR/backtrace-api-v0.rs:29:18 (main) diff --git a/src/tools/miri/tests/pass/backtrace/backtrace-api-v1.rs b/src/tools/miri/tests/pass/backtrace/backtrace-api-v1.rs new file mode 100644 index 0000000000000..1e35574b39b6c --- /dev/null +++ b/src/tools/miri/tests/pass/backtrace/backtrace-api-v1.rs @@ -0,0 +1,82 @@ +//@normalize-stderr-test: "::<.*>" -> "" + +#[inline(never)] +fn func_a() -> Box<[*mut ()]> { + func_b::() +} +#[inline(never)] +fn func_b() -> Box<[*mut ()]> { + func_c() +} + +macro_rules! invoke_func_d { + () => { + func_d() + }; +} + +#[inline(never)] +fn func_c() -> Box<[*mut ()]> { + invoke_func_d!() +} +#[inline(never)] +fn func_d() -> Box<[*mut ()]> { + unsafe { + let count = miri_backtrace_size(0); + let mut buf = vec![std::ptr::null_mut(); count]; + miri_get_backtrace(1, buf.as_mut_ptr()); + buf.into() + } +} + +fn main() { + let mut seen_main = false; + let frames = func_a(); + for frame in frames.into_iter() { + let miri_frame = unsafe { miri_resolve_frame(*frame, 1) }; + + let mut name = vec![0; miri_frame.name_len]; + let mut filename = vec![0; miri_frame.filename_len]; + + unsafe { + miri_resolve_frame_names(*frame, 0, name.as_mut_ptr(), filename.as_mut_ptr()); + } + + let name = String::from_utf8(name).unwrap(); + let filename = String::from_utf8(filename).unwrap(); + + if name == "func_a" { + assert_eq!(func_a as *mut (), miri_frame.fn_ptr); + } + + // Print every frame to stderr. + let out = format!("{}:{}:{} ({})", filename, miri_frame.lineno, miri_frame.colno, name); + eprintln!("{}", out); + // Print the 'main' frame (and everything before it) to stdout, skipping + // the printing of internal (and possibly fragile) libstd frames. + if !seen_main { + println!("{}", out); + seen_main = name == "main"; + } + } +} + +// This goes at the bottom of the file so that we can change it +// without disturbing line numbers of the functions in the backtrace. + +extern "Rust" { + fn miri_backtrace_size(flags: u64) -> usize; + fn miri_get_backtrace(flags: u64, buf: *mut *mut ()); + fn miri_resolve_frame(ptr: *mut (), flags: u64) -> MiriFrame; + fn miri_resolve_frame_names(ptr: *mut (), flags: u64, name_buf: *mut u8, filename_buf: *mut u8); +} + +#[derive(Debug)] +#[repr(C)] +struct MiriFrame { + name_len: usize, + filename_len: usize, + lineno: u32, + colno: u32, + fn_ptr: *mut (), +} diff --git a/src/tools/miri/tests/pass/backtrace/backtrace-api-v1.stderr b/src/tools/miri/tests/pass/backtrace/backtrace-api-v1.stderr new file mode 100644 index 0000000000000..7dc281af31ddc --- /dev/null +++ b/src/tools/miri/tests/pass/backtrace/backtrace-api-v1.stderr @@ -0,0 +1,5 @@ +$DIR/backtrace-api-v1.rs:LL:CC (func_d) +$DIR/backtrace-api-v1.rs:LL:CC (func_c) +$DIR/backtrace-api-v1.rs:LL:CC (func_b) +$DIR/backtrace-api-v1.rs:LL:CC (func_a) +$DIR/backtrace-api-v1.rs:LL:CC RUSTLIB/core/src/ops/function.rs:LL:CC (>::call_once - RUSTLIB/std/src/sys_common/backtrace.rs:LL:CC (std::sys_common::backtrace::__rust_begin_short_backtrace) diff --git a/src/tools/miri/tests/pass/backtrace/backtrace-api-v1.stdout b/src/tools/miri/tests/pass/backtrace/backtrace-api-v1.stdout new file mode 100644 index 0000000000000..0d2ae3b516a87 --- /dev/null +++ b/src/tools/miri/tests/pass/backtrace/backtrace-api-v1.stdout @@ -0,0 +1,5 @@ +$DIR/backtrace-api-v1.rs:27:9 (func_d) +$DIR/backtrace-api-v1.rs:20:5 (func_c) +$DIR/backtrace-api-v1.rs:9:5 (func_b) +$DIR/backtrace-api-v1.rs:5:5 (func_a) +$DIR/backtrace-api-v1.rs:34:18 (main) diff --git a/src/tools/miri/tests/pass/backtrace/backtrace-global-alloc.rs b/src/tools/miri/tests/pass/backtrace/backtrace-global-alloc.rs new file mode 100644 index 0000000000000..6660e5bb57c09 --- /dev/null +++ b/src/tools/miri/tests/pass/backtrace/backtrace-global-alloc.rs @@ -0,0 +1,12 @@ +//@compile-flags: -Zmiri-disable-isolation +//@rustc-env: RUST_BACKTRACE=1 + +use std::alloc::System; +use std::backtrace::Backtrace; + +#[global_allocator] +static GLOBAL_ALLOCATOR: System = System; + +fn main() { + eprint!("{}", Backtrace::capture()); +} diff --git a/src/tools/miri/tests/pass/backtrace/backtrace-global-alloc.stderr b/src/tools/miri/tests/pass/backtrace/backtrace-global-alloc.stderr new file mode 100644 index 0000000000000..c48061d64d088 --- /dev/null +++ b/src/tools/miri/tests/pass/backtrace/backtrace-global-alloc.stderr @@ -0,0 +1,28 @@ + 0: main + at $DIR/backtrace-global-alloc.rs:LL:CC + 1: >::call_once - shim(fn()) + at RUSTLIB/core/src/ops/function.rs:LL:CC + 2: std::sys_common::backtrace::__rust_begin_short_backtrace + at RUSTLIB/std/src/sys_common/backtrace.rs:LL:CC + 3: std::rt::lang_start::{closure#0} + at RUSTLIB/std/src/rt.rs:LL:CC + 4: std::ops::function::impls::call_once + at RUSTLIB/core/src/ops/function.rs:LL:CC + 5: std::panicking::r#try::do_call + at RUSTLIB/std/src/panicking.rs:LL:CC + 6: std::panicking::r#try + at RUSTLIB/std/src/panicking.rs:LL:CC + 7: std::panic::catch_unwind + at RUSTLIB/std/src/panic.rs:LL:CC + 8: std::rt::lang_start_internal::{closure#2} + at RUSTLIB/std/src/rt.rs:LL:CC + 9: std::panicking::r#try::do_call + at RUSTLIB/std/src/panicking.rs:LL:CC + 10: std::panicking::r#try + at RUSTLIB/std/src/panicking.rs:LL:CC + 11: std::panic::catch_unwind + at RUSTLIB/std/src/panic.rs:LL:CC + 12: std::rt::lang_start_internal + at RUSTLIB/std/src/rt.rs:LL:CC + 13: std::rt::lang_start + at RUSTLIB/std/src/rt.rs:LL:CC diff --git a/src/tools/miri/tests/pass/backtrace/backtrace-std.rs b/src/tools/miri/tests/pass/backtrace/backtrace-std.rs new file mode 100644 index 0000000000000..b2a5ac180298c --- /dev/null +++ b/src/tools/miri/tests/pass/backtrace/backtrace-std.rs @@ -0,0 +1,32 @@ +//@compile-flags: -Zmiri-disable-isolation +//@rustc-env: RUST_BACKTRACE=1 + +use std::backtrace::Backtrace; + +#[inline(never)] +fn func_a() -> Backtrace { + func_b::() +} +#[inline(never)] +fn func_b() -> Backtrace { + func_c() +} + +macro_rules! invoke_func_d { + () => { + func_d() + }; +} + +#[inline(never)] +fn func_c() -> Backtrace { + invoke_func_d!() +} +#[inline(never)] +fn func_d() -> Backtrace { + Backtrace::capture() +} + +fn main() { + eprint!("{}", func_a()); +} diff --git a/src/tools/miri/tests/pass/backtrace/backtrace-std.stderr b/src/tools/miri/tests/pass/backtrace/backtrace-std.stderr new file mode 100644 index 0000000000000..4596cadb958d8 --- /dev/null +++ b/src/tools/miri/tests/pass/backtrace/backtrace-std.stderr @@ -0,0 +1,36 @@ + 0: func_d + at $DIR/backtrace-std.rs:LL:CC + 1: func_c + at $DIR/backtrace-std.rs:LL:CC + 2: func_b + at $DIR/backtrace-std.rs:LL:CC + 3: func_a + at $DIR/backtrace-std.rs:LL:CC + 4: main + at $DIR/backtrace-std.rs:LL:CC + 5: >::call_once - shim(fn()) + at RUSTLIB/core/src/ops/function.rs:LL:CC + 6: std::sys_common::backtrace::__rust_begin_short_backtrace + at RUSTLIB/std/src/sys_common/backtrace.rs:LL:CC + 7: std::rt::lang_start::{closure#0} + at RUSTLIB/std/src/rt.rs:LL:CC + 8: std::ops::function::impls::call_once + at RUSTLIB/core/src/ops/function.rs:LL:CC + 9: std::panicking::r#try::do_call + at RUSTLIB/std/src/panicking.rs:LL:CC + 10: std::panicking::r#try + at RUSTLIB/std/src/panicking.rs:LL:CC + 11: std::panic::catch_unwind + at RUSTLIB/std/src/panic.rs:LL:CC + 12: std::rt::lang_start_internal::{closure#2} + at RUSTLIB/std/src/rt.rs:LL:CC + 13: std::panicking::r#try::do_call + at RUSTLIB/std/src/panicking.rs:LL:CC + 14: std::panicking::r#try + at RUSTLIB/std/src/panicking.rs:LL:CC + 15: std::panic::catch_unwind + at RUSTLIB/std/src/panic.rs:LL:CC + 16: std::rt::lang_start_internal + at RUSTLIB/std/src/rt.rs:LL:CC + 17: std::rt::lang_start + at RUSTLIB/std/src/rt.rs:LL:CC diff --git a/src/tools/miri/tests/pass/bad_substs.rs b/src/tools/miri/tests/pass/bad_substs.rs new file mode 100644 index 0000000000000..d8da2de5d6df9 --- /dev/null +++ b/src/tools/miri/tests/pass/bad_substs.rs @@ -0,0 +1,4 @@ +fn main() { + let f: fn(i32) -> Option = Some::; + f(42); +} diff --git a/src/tools/miri/tests/pass/binary-heap.rs b/src/tools/miri/tests/pass/binary-heap.rs new file mode 100644 index 0000000000000..278038d8ad321 --- /dev/null +++ b/src/tools/miri/tests/pass/binary-heap.rs @@ -0,0 +1,37 @@ +use std::collections::BinaryHeap; +use std::iter::Iterator; + +fn zero_sized_push() { + const N: usize = 8; + + for len in 0..N { + let mut tester = BinaryHeap::with_capacity(len); + assert_eq!(tester.len(), 0); + assert!(tester.capacity() >= len); + for _ in 0..len { + tester.push(()); + } + assert_eq!(tester.len(), len); + assert_eq!(tester.iter().count(), len); + tester.clear(); + } +} + +fn drain() { + let mut heap = (0..128i32).collect::>(); + + assert!(!heap.is_empty()); + + let mut sum = 0; + for x in heap.drain() { + sum += x; + } + assert_eq!(sum, 127 * 128 / 2); + + assert!(heap.is_empty()); +} + +fn main() { + zero_sized_push(); + drain(); +} diff --git a/src/tools/miri/tests/pass/binops.rs b/src/tools/miri/tests/pass/binops.rs new file mode 100644 index 0000000000000..0988d7ccc4c6e --- /dev/null +++ b/src/tools/miri/tests/pass/binops.rs @@ -0,0 +1,78 @@ +// Binop corner cases + +fn test_nil() { + assert_eq!((), ()); + assert!((!(() != ()))); + assert!((!(() < ()))); + assert!((() <= ())); + assert!((!(() > ()))); + assert!((() >= ())); +} + +fn test_bool() { + assert!((!(true < false))); + assert!((!(true <= false))); + assert!((true > false)); + assert!((true >= false)); + + assert!((false < true)); + assert!((false <= true)); + assert!((!(false > true))); + assert!((!(false >= true))); + + // Bools support bitwise binops + assert_eq!(false & false, false); + assert_eq!(true & false, false); + assert_eq!(true & true, true); + assert_eq!(false | false, false); + assert_eq!(true | false, true); + assert_eq!(true | true, true); + assert_eq!(false ^ false, false); + assert_eq!(true ^ false, true); + assert_eq!(true ^ true, false); +} + +fn test_ptr() { + unsafe { + let p1: *const u8 = ::std::mem::transmute(0_usize); + let p2: *const u8 = ::std::mem::transmute(0_usize); + let p3: *const u8 = ::std::mem::transmute(1_usize); + + assert_eq!(p1, p2); + assert!(p1 != p3); + assert!(p1 < p3); + assert!(p1 <= p3); + assert!(p3 > p1); + assert!(p3 >= p3); + assert!(p1 <= p2); + assert!(p1 >= p2); + } +} + +#[derive(PartialEq, Debug)] +struct P { + x: isize, + y: isize, +} + +fn p(x: isize, y: isize) -> P { + P { x: x, y: y } +} + +fn test_class() { + let q = p(1, 2); + let mut r = p(1, 2); + + assert_eq!(q, r); + r.y = 17; + assert!((r.y != q.y)); + assert_eq!(r.y, 17); + assert!((q != r)); +} + +pub fn main() { + test_nil(); + test_bool(); + test_ptr(); + test_class(); +} diff --git a/src/tools/miri/tests/pass/bools.rs b/src/tools/miri/tests/pass/bools.rs new file mode 100644 index 0000000000000..30fc14704d9d0 --- /dev/null +++ b/src/tools/miri/tests/pass/bools.rs @@ -0,0 +1,29 @@ +fn boolean() -> bool { + true +} + +fn if_false() -> i64 { + let c = false; + if c { 1 } else { 0 } +} + +fn if_true() -> i64 { + let c = true; + if c { 1 } else { 0 } +} + +fn match_bool() -> i16 { + let b = true; + match b { + true => 1, + _ => 0, + } +} + +fn main() { + assert!(boolean()); + assert_eq!(if_false(), 0); + assert_eq!(if_true(), 1); + assert_eq!(match_bool(), 1); + assert_eq!(true == true, true); +} diff --git a/src/tools/miri/tests/pass/box-custom-alloc.rs b/src/tools/miri/tests/pass/box-custom-alloc.rs new file mode 100644 index 0000000000000..ef432a86d460a --- /dev/null +++ b/src/tools/miri/tests/pass/box-custom-alloc.rs @@ -0,0 +1,87 @@ +#![allow(incomplete_features)] // for trait upcasting +#![feature(allocator_api, trait_upcasting)] + +use std::alloc::Layout; +use std::alloc::{AllocError, Allocator}; +use std::cell::Cell; +use std::mem::MaybeUninit; +use std::ptr::{self, NonNull}; + +struct OnceAlloc<'a> { + space: Cell<&'a mut [MaybeUninit]>, +} + +unsafe impl<'shared, 'a: 'shared> Allocator for &'shared OnceAlloc<'a> { + fn allocate(&self, layout: Layout) -> Result, AllocError> { + let space = self.space.replace(&mut []); + + let (ptr, len) = (space.as_mut_ptr(), space.len()); + + if ptr.align_offset(layout.align()) != 0 || len < layout.size() { + return Err(AllocError); + } + + let slice_ptr = ptr::slice_from_raw_parts_mut(ptr as *mut u8, len); + unsafe { Ok(NonNull::new_unchecked(slice_ptr)) } + } + + unsafe fn deallocate(&self, _ptr: NonNull, _layout: Layout) {} +} + +trait MyTrait { + fn hello(&self) -> u8; +} + +impl MyTrait for [u8; 1] { + fn hello(&self) -> u8 { + self[0] + } +} + +trait TheTrait: MyTrait {} + +impl TheTrait for [u8; 1] {} + +/// `Box` is a `ScalarPair` where the 2nd component is the allocator. +fn test1() { + let mut space = vec![MaybeUninit::new(0); 1]; + let once_alloc = OnceAlloc { space: Cell::new(&mut space[..]) }; + + let boxed = Box::new_in([42u8; 1], &once_alloc); + let _val = *boxed; + let with_dyn: Box = boxed; + assert_eq!(42, with_dyn.hello()); + let with_dyn: Box = with_dyn; // upcast + assert_eq!(42, with_dyn.hello()); +} + +// Make the allocator itself so big that the Box is not even a ScalarPair any more. +struct OnceAllocRef<'s, 'a>(&'s OnceAlloc<'a>, u64); + +unsafe impl<'shared, 'a: 'shared> Allocator for OnceAllocRef<'shared, 'a> { + fn allocate(&self, layout: Layout) -> Result, AllocError> { + self.0.allocate(layout) + } + + unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { + self.0.deallocate(ptr, layout) + } +} + +/// `Box` is an `Aggregate`. +fn test2() { + let mut space = vec![MaybeUninit::new(0); 1]; + let once_alloc = OnceAlloc { space: Cell::new(&mut space[..]) }; + + let boxed = Box::new_in([42u8; 1], OnceAllocRef(&once_alloc, 0)); + let _val = *boxed; + let with_dyn: Box = boxed; + assert_eq!(42, with_dyn.hello()); + let with_dyn: Box = with_dyn; // upcast + assert_eq!(42, with_dyn.hello()); +} + +fn main() { + test1(); + test2(); +} diff --git a/src/tools/miri/tests/pass/box.rs b/src/tools/miri/tests/pass/box.rs new file mode 100644 index 0000000000000..7bbe7be516b9a --- /dev/null +++ b/src/tools/miri/tests/pass/box.rs @@ -0,0 +1,59 @@ +#![feature(ptr_internals)] + +fn main() { + into_raw(); + into_unique(); + boxed_pair_to_vec(); +} + +fn into_raw() { + unsafe { + let b = Box::new(4i32); + let r = Box::into_raw(b); + + // "lose the tag" + let r2 = ((r as usize) + 0) as *mut i32; + *(&mut *r2) = 7; + + // Use original ptr again + *(&mut *r) = 17; + drop(Box::from_raw(r)); + } +} + +fn into_unique() { + unsafe { + let b = Box::new(4i32); + let u = Box::into_unique(b).0; + + // "lose the tag" + let r = ((u.as_ptr() as usize) + 0) as *mut i32; + *(&mut *r) = 7; + + // Use original ptr again. + drop(Box::from_raw(u.as_ptr())); + } +} + +fn boxed_pair_to_vec() { + #[repr(C)] + #[derive(Debug)] + struct PairFoo { + fst: Foo, + snd: Foo, + } + + #[derive(Debug)] + struct Foo(u64); + fn reinterstruct(box_pair: Box) -> Vec { + let ref_pair = Box::leak(box_pair) as *mut PairFoo; + let ptr_foo = unsafe { std::ptr::addr_of_mut!((*ref_pair).fst) }; + unsafe { Vec::from_raw_parts(ptr_foo, 2, 2) } + } + + let pair_foo = Box::new(PairFoo { fst: Foo(42), snd: Foo(1337) }); + println!("pair_foo = {:?}", pair_foo); + for (n, foo) in reinterstruct(pair_foo).into_iter().enumerate() { + println!("foo #{} = {:?}", n, foo); + } +} diff --git a/src/tools/miri/tests/pass/box.stderr b/src/tools/miri/tests/pass/box.stderr new file mode 100644 index 0000000000000..0001a8dd6eb33 --- /dev/null +++ b/src/tools/miri/tests/pass/box.stderr @@ -0,0 +1,33 @@ +warning: integer-to-pointer cast + --> $DIR/box.rs:LL:CC + | +LL | let r2 = ((r as usize) + 0) as *mut i32; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ integer-to-pointer cast + | + = help: This program is using integer-to-pointer casts or (equivalently) `ptr::from_exposed_addr`, + = help: which means that Miri might miss pointer bugs in this program. + = help: See https://doc.rust-lang.org/nightly/std/ptr/fn.from_exposed_addr.html for more details on that operation. + = help: To ensure that Miri does not miss bugs in your program, use Strict Provenance APIs (https://doc.rust-lang.org/nightly/std/ptr/index.html#strict-provenance, https://crates.io/crates/sptr) instead. + = help: You can then pass the `-Zmiri-strict-provenance` flag to Miri, to ensure you are not relying on `from_exposed_addr` semantics. + = help: Alternatively, the `-Zmiri-permissive-provenance` flag disables this warning. + = note: BACKTRACE: + = note: inside `into_raw` at $DIR/box.rs:LL:CC +note: inside `main` at $DIR/box.rs:LL:CC + --> $DIR/box.rs:LL:CC + | +LL | into_raw(); + | ^^^^^^^^^^ + +warning: integer-to-pointer cast + --> $DIR/box.rs:LL:CC + | +LL | let r = ((u.as_ptr() as usize) + 0) as *mut i32; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ integer-to-pointer cast + | + = note: inside `into_unique` at $DIR/box.rs:LL:CC +note: inside `main` at $DIR/box.rs:LL:CC + --> $DIR/box.rs:LL:CC + | +LL | into_unique(); + | ^^^^^^^^^^^^^ + diff --git a/src/tools/miri/tests/pass/box.stdout b/src/tools/miri/tests/pass/box.stdout new file mode 100644 index 0000000000000..230ef368da644 --- /dev/null +++ b/src/tools/miri/tests/pass/box.stdout @@ -0,0 +1,3 @@ +pair_foo = PairFoo { fst: Foo(42), snd: Foo(1337) } +foo #0 = Foo(42) +foo #1 = Foo(1337) diff --git a/src/tools/miri/tests/pass/btreemap.rs b/src/tools/miri/tests/pass/btreemap.rs new file mode 100644 index 0000000000000..040c648d4747b --- /dev/null +++ b/src/tools/miri/tests/pass/btreemap.rs @@ -0,0 +1,54 @@ +//@compile-flags: -Zmiri-strict-provenance +#![feature(btree_drain_filter)] +use std::collections::{BTreeMap, BTreeSet}; +use std::mem; + +#[derive(PartialEq, Eq, PartialOrd, Ord)] +pub enum Foo { + A(&'static str), + _B, + _C, +} + +// Gather all references from a mutable iterator and make sure Miri notices if +// using them is dangerous. +fn test_all_refs<'a, T: 'a>(dummy: &mut T, iter: impl Iterator) { + // Gather all those references. + let mut refs: Vec<&mut T> = iter.collect(); + // Use them all. Twice, to be sure we got all interleavings. + for r in refs.iter_mut() { + std::mem::swap(dummy, r); + } + for r in refs { + std::mem::swap(dummy, r); + } +} + +pub fn main() { + let mut b = BTreeSet::new(); + b.insert(Foo::A("\'")); + b.insert(Foo::A("/=")); + b.insert(Foo::A("#")); + b.insert(Foo::A("0o")); + assert!(b.remove(&Foo::A("/="))); + assert!(!b.remove(&Foo::A("/="))); + + // Also test a lower-alignment type, where the NodeHeader overlaps with + // the keys. + let mut b = BTreeSet::new(); + b.insert(1024u16); + b.insert(7u16); + + let mut b = BTreeMap::new(); + b.insert(format!("bar"), 1024); + b.insert(format!("baz"), 7); + for i in 0..60 { + b.insert(format!("key{}", i), i); + } + test_all_refs(&mut 13, b.values_mut()); + + // Test forgetting the drain. + let mut d = b.drain_filter(|_, i| *i < 30); + d.next().unwrap(); + mem::forget(d); +} diff --git a/src/tools/miri/tests/pass/c_enums.rs b/src/tools/miri/tests/pass/c_enums.rs new file mode 100644 index 0000000000000..16b795342eab8 --- /dev/null +++ b/src/tools/miri/tests/pass/c_enums.rs @@ -0,0 +1,34 @@ +enum Foo { + Bar = 42, + Baz, + Quux = 100, +} + +enum Signed { + Bar = -42, + Baz, + Quux = 100, +} + +fn foo() -> [u8; 3] { + let baz = Foo::Baz; // let-expansion changes the MIR significantly + [Foo::Bar as u8, baz as u8, Foo::Quux as u8] +} + +fn signed() -> [i8; 3] { + let baz = Signed::Baz; // let-expansion changes the MIR significantly + [Signed::Bar as i8, baz as i8, Signed::Quux as i8] +} + +fn unsafe_match() -> bool { + match unsafe { std::mem::transmute::(43) } { + Foo::Baz => true, + _ => false, + } +} + +fn main() { + assert_eq!(foo(), [42, 43, 100]); + assert_eq!(signed(), [-42, -41, 100]); + assert!(unsafe_match()); +} diff --git a/src/tools/miri/tests/pass/calls.rs b/src/tools/miri/tests/pass/calls.rs new file mode 100644 index 0000000000000..014d1d3acab7f --- /dev/null +++ b/src/tools/miri/tests/pass/calls.rs @@ -0,0 +1,43 @@ +fn call() -> i32 { + fn increment(x: i32) -> i32 { + x + 1 + } + increment(1) +} + +fn factorial_recursive() -> i64 { + fn fact(n: i64) -> i64 { + if n == 0 { 1 } else { n * fact(n - 1) } + } + fact(10) +} + +fn call_generic() -> (i16, bool) { + fn id(t: T) -> T { + t + } + (id(42), id(true)) +} + +// Test calling a very simple function from the standard library. +fn cross_crate_fn_call() -> i64 { + if 1i32.is_positive() { 1 } else { 0 } +} + +const fn foo(i: i64) -> i64 { + *&i + 1 +} + +fn const_fn_call() -> i64 { + let x = 5 + foo(5); + assert_eq!(x, 11); + x +} + +fn main() { + assert_eq!(call(), 2); + assert_eq!(factorial_recursive(), 3628800); + assert_eq!(call_generic(), (42, true)); + assert_eq!(cross_crate_fn_call(), 1); + assert_eq!(const_fn_call(), 11); +} diff --git a/src/tools/miri/tests/pass/cast-rfc0401-vtable-kinds.rs b/src/tools/miri/tests/pass/cast-rfc0401-vtable-kinds.rs new file mode 100644 index 0000000000000..d76c23633da1b --- /dev/null +++ b/src/tools/miri/tests/pass/cast-rfc0401-vtable-kinds.rs @@ -0,0 +1,53 @@ +// Check that you can cast between different pointers to trait objects +// whose vtable have the same kind (both lengths, or both trait pointers). + +trait Foo { + fn foo(&self, _: T) -> u32 { + 42 + } +} + +trait Bar { + fn bar(&self) { + println!("Bar!"); + } +} + +impl Foo for () {} +impl Foo for u32 { + fn foo(&self, _: u32) -> u32 { + self + 43 + } +} +impl Bar for () {} + +unsafe fn round_trip_and_call<'a>(t: *const (dyn Foo + 'a)) -> u32 { + let foo_e: *const dyn Foo = t as *const _; + let r_1 = foo_e as *mut dyn Foo; + + (&*r_1).foo(0) +} + +#[repr(C)] +struct FooS(T); +#[repr(C)] +struct BarS(T); + +fn foo_to_bar(u: *const FooS) -> *const BarS { + u as *const BarS +} + +fn main() { + let x = 4u32; + let y: &dyn Foo = &x; + let fl = unsafe { round_trip_and_call(y as *const dyn Foo) }; + assert_eq!(fl, (43 + 4)); + + let s = FooS([0, 1, 2]); + let u: &FooS<[u32]> = &s; + let u: *const FooS<[u32]> = u; + let bar_ref: *const BarS<[u32]> = foo_to_bar(u); + let z: &BarS<[u32]> = unsafe { &*bar_ref }; + assert_eq!(&z.0, &[0, 1, 2]); + // If validation fails here, that's likely because an immutable suspension is recovered mutably. +} diff --git a/src/tools/miri/tests/pass/cast_fn_ptr.rs b/src/tools/miri/tests/pass/cast_fn_ptr.rs new file mode 100644 index 0000000000000..8954048f4262c --- /dev/null +++ b/src/tools/miri/tests/pass/cast_fn_ptr.rs @@ -0,0 +1,7 @@ +fn main() { + fn f(_: *const u8) {} + + let g = unsafe { std::mem::transmute::(f) }; + + g(&42 as *const _); +} diff --git a/src/tools/miri/tests/pass/cast_fn_ptr_unsafe.rs b/src/tools/miri/tests/pass/cast_fn_ptr_unsafe.rs new file mode 100644 index 0000000000000..0cabb369bfdd9 --- /dev/null +++ b/src/tools/miri/tests/pass/cast_fn_ptr_unsafe.rs @@ -0,0 +1,8 @@ +fn main() { + fn f() {} + + let g = f as fn() as unsafe fn(); + unsafe { + g(); + } +} diff --git a/src/tools/miri/tests/pass/catch.rs b/src/tools/miri/tests/pass/catch.rs new file mode 100644 index 0000000000000..4ede23e68ce25 --- /dev/null +++ b/src/tools/miri/tests/pass/catch.rs @@ -0,0 +1,7 @@ +use std::panic::{catch_unwind, AssertUnwindSafe}; + +fn main() { + let mut i = 3; + let _val = catch_unwind(AssertUnwindSafe(|| i -= 2)); + println!("{}", i); +} diff --git a/src/tools/miri/tests/pass/catch.stdout b/src/tools/miri/tests/pass/catch.stdout new file mode 100644 index 0000000000000..d00491fd7e5bb --- /dev/null +++ b/src/tools/miri/tests/pass/catch.stdout @@ -0,0 +1 @@ +1 diff --git a/src/tools/miri/tests/pass/cfg_miri.rs b/src/tools/miri/tests/pass/cfg_miri.rs new file mode 100644 index 0000000000000..558b9a4f50db9 --- /dev/null +++ b/src/tools/miri/tests/pass/cfg_miri.rs @@ -0,0 +1,3 @@ +fn main() { + assert!(cfg!(miri)); +} diff --git a/src/tools/miri/tests/pass/char.rs b/src/tools/miri/tests/pass/char.rs new file mode 100644 index 0000000000000..5524f0ae7abea --- /dev/null +++ b/src/tools/miri/tests/pass/char.rs @@ -0,0 +1,7 @@ +fn main() { + let c = 'x'; + assert_eq!(c, 'x'); + assert!('a' < 'z'); + assert!('1' < '9'); + assert_eq!(std::char::from_u32('x' as u32), Some('x')); +} diff --git a/src/tools/miri/tests/pass/closure-drop.rs b/src/tools/miri/tests/pass/closure-drop.rs new file mode 100644 index 0000000000000..9f9454b4c71c5 --- /dev/null +++ b/src/tools/miri/tests/pass/closure-drop.rs @@ -0,0 +1,26 @@ +struct Foo<'a>(&'a mut bool); + +impl<'a> Drop for Foo<'a> { + fn drop(&mut self) { + *self.0 = true; + } +} + +fn f(t: T) { + t() +} + +fn main() { + let mut ran_drop = false; + { + let x = Foo(&mut ran_drop); + // this closure never by val uses its captures + // so it's basically a fn(&self) + // the shim used to not drop the `x` + let x = move || { + let _val = x; + }; + f(x); + } + assert!(ran_drop); +} diff --git a/src/tools/miri/tests/pass/closure-field-ty.rs b/src/tools/miri/tests/pass/closure-field-ty.rs new file mode 100644 index 0000000000000..1c90a15f8c5ca --- /dev/null +++ b/src/tools/miri/tests/pass/closure-field-ty.rs @@ -0,0 +1,10 @@ +// miri issue #304 +fn main() { + let mut y = 0; + { + let mut box_maybe_closure = Box::new(None); + *box_maybe_closure = Some(|| y += 1); + (box_maybe_closure.unwrap())(); + } + assert_eq!(y, 1); +} diff --git a/src/tools/miri/tests/pass/closures.rs b/src/tools/miri/tests/pass/closures.rs new file mode 100644 index 0000000000000..40aedbcaf4545 --- /dev/null +++ b/src/tools/miri/tests/pass/closures.rs @@ -0,0 +1,129 @@ +fn simple() -> i32 { + let y = 10; + let f = |x| x + y; + f(2) +} + +fn crazy_closure() -> (i32, i32, i32) { + fn inner(t: T) -> (i32, T, T) { + struct NonCopy; + let x = NonCopy; + + let a = 2; + let b = 40; + let f = move |y, z, asdf| { + drop(x); + (a + b + y + z, asdf, t) + }; + f(a, b, t) + } + + inner(10) +} + +fn closure_arg_adjustment_problem() -> i64 { + fn once(f: F) { + f(2); + } + let mut y = 1; + { + let f = |x| y += x; + once(f); + } + y +} + +fn fn_once_closure_with_multiple_args() -> i64 { + fn once i64>(f: F) -> i64 { + f(2, 3) + } + let y = 1; + { + let f = |x, z| x + y + z; + once(f) + } +} + +fn boxed_fn_once(f: Box i32>) -> i32 { + f() +} + +fn box_dyn() { + let x: Box i32> = Box::new(|x| x * 2); + assert_eq!(x(21), 42); + let mut i = 5; + { + let mut x: Box = Box::new(|| i *= 2); + x(); + x(); + } + assert_eq!(i, 20); +} + +fn fn_item_as_closure_trait_object() { + fn foo() {} + let f: &dyn Fn() = &foo; + f(); +} + +fn fn_item_with_args_as_closure_trait_object() { + fn foo(i: i32) { + assert_eq!(i, 42); + } + let f: &dyn Fn(i32) = &foo; + f(42); +} + +fn fn_item_with_multiple_args_as_closure_trait_object() { + fn foo(i: i32, j: i32) { + assert_eq!(i, 42); + assert_eq!(j, 55); + } + + fn bar(i: i32, j: i32, k: f32) { + assert_eq!(i, 42); + assert_eq!(j, 55); + assert_eq!(k, 3.14159) + } + let f: &dyn Fn(i32, i32) = &foo; + f(42, 55); + let f: &dyn Fn(i32, i32, f32) = &bar; + f(42, 55, 3.14159); +} + +fn fn_ptr_as_closure_trait_object() { + fn foo() {} + fn bar(u: u32) { + assert_eq!(u, 42); + } + fn baa(u: u32, f: f32) { + assert_eq!(u, 42); + assert_eq!(f, 3.141); + } + let f: &dyn Fn() = &(foo as fn()); + f(); + let f: &dyn Fn(u32) = &(bar as fn(u32)); + f(42); + let f: &dyn Fn(u32, f32) = &(baa as fn(u32, f32)); + f(42, 3.141); +} + +fn main() { + assert_eq!(simple(), 12); + assert_eq!(crazy_closure(), (84, 10, 10)); + assert_eq!(closure_arg_adjustment_problem(), 3); + assert_eq!(fn_once_closure_with_multiple_args(), 6); + assert_eq!( + boxed_fn_once(Box::new({ + let x = 13; + move || x + })), + 13, + ); + + box_dyn(); + fn_item_as_closure_trait_object(); + fn_item_with_args_as_closure_trait_object(); + fn_item_with_multiple_args_as_closure_trait_object(); + fn_ptr_as_closure_trait_object(); +} diff --git a/src/tools/miri/tests/pass/coerce_non_capture_closure_to_fn_ptr.rs b/src/tools/miri/tests/pass/coerce_non_capture_closure_to_fn_ptr.rs new file mode 100644 index 0000000000000..fde120db027b1 --- /dev/null +++ b/src/tools/miri/tests/pass/coerce_non_capture_closure_to_fn_ptr.rs @@ -0,0 +1,39 @@ +static FOO: fn() = || assert_ne!(42, 43); +static BAR: fn(i32, i32) = |a, b| assert_ne!(a, b); + +// use to first make the closure FnOnce() before making it fn() +fn force_once0 R>(f: F) -> F { + f +} +fn force_once1 R>(f: F) -> F { + f +} +fn force_mut0 R>(f: F) -> F { + f +} +fn force_mut1 R>(f: F) -> F { + f +} + +fn main() { + FOO(); + BAR(44, 45); + let bar: unsafe fn(i32, i32) = BAR; + unsafe { bar(46, 47) }; + let boo: &dyn Fn(i32, i32) = &BAR; + boo(48, 49); + + let f: fn() = || {}; + f(); + let f = force_once0(|| {}) as fn(); + f(); + let f = force_mut0(|| {}) as fn(); + f(); + + let g: fn(i32) = |i| assert_eq!(i, 2); + g(2); + let g = force_once1(|i| assert_eq!(i, 2)) as fn(i32); + g(2); + let g = force_mut1(|i| assert_eq!(i, 2)) as fn(i32); + g(2); +} diff --git a/src/tools/miri/tests/pass/coercions.rs b/src/tools/miri/tests/pass/coercions.rs new file mode 100644 index 0000000000000..db0d2ffd44bcf --- /dev/null +++ b/src/tools/miri/tests/pass/coercions.rs @@ -0,0 +1,76 @@ +#![feature(coerce_unsized, unsize)] + +use std::marker::Unsize; +use std::ops::CoerceUnsized; + +fn identity_coercion(x: &(dyn Fn(u32) -> u32 + Send)) -> &dyn Fn(u32) -> u32 { + x +} +fn fn_coercions(f: &fn(u32) -> u32) -> (unsafe fn(u32) -> u32, &(dyn Fn(u32) -> u32 + Send)) { + (*f, f) +} + +fn simple_array_coercion(x: &[u8; 3]) -> &[u8] { + x +} + +fn square(a: u32) -> u32 { + a * a +} + +#[derive(PartialEq, Eq)] +struct PtrWrapper<'a, T: 'a + ?Sized>(u32, u32, (), &'a T); +impl<'a, T: ?Sized + Unsize, U: ?Sized> CoerceUnsized> for PtrWrapper<'a, T> {} + +struct TrivPtrWrapper<'a, T: 'a + ?Sized>(&'a T); +impl<'a, T: ?Sized + Unsize, U: ?Sized> CoerceUnsized> + for TrivPtrWrapper<'a, T> +{ +} + +fn coerce_ptr_wrapper(p: PtrWrapper<[u8; 3]>) -> PtrWrapper<[u8]> { + p +} + +fn coerce_triv_ptr_wrapper(p: TrivPtrWrapper<[u8; 3]>) -> TrivPtrWrapper<[u8]> { + p +} + +fn coerce_fat_ptr_wrapper( + p: PtrWrapper u32 + Send>, +) -> PtrWrapper u32> { + p +} + +fn coerce_ptr_wrapper_poly<'a, T, Trait: ?Sized>(p: PtrWrapper<'a, T>) -> PtrWrapper<'a, Trait> +where + PtrWrapper<'a, T>: CoerceUnsized>, +{ + p +} + +fn main() { + let a = [0, 1, 2]; + let square_local: fn(u32) -> u32 = square; + let (f, g) = fn_coercions(&square_local); + // cannot use `square as *const ()` because we can't know whether the compiler duplicates + // functions, so two function pointers are only equal if they result from the same function + // to function pointer cast + assert_eq!(f as *const (), square_local as *const ()); + assert_eq!(g(4), 16); + assert_eq!(identity_coercion(g)(5), 25); + + assert_eq!(simple_array_coercion(&a), &a); + let w = coerce_ptr_wrapper(PtrWrapper(2, 3, (), &a)); + assert!(w == PtrWrapper(2, 3, (), &a) as PtrWrapper<[u8]>); + + let w = coerce_triv_ptr_wrapper(TrivPtrWrapper(&a)); + assert_eq!(&w.0, &a); + + let z = coerce_fat_ptr_wrapper(PtrWrapper(2, 3, (), &square_local)); + assert_eq!((z.3)(6), 36); + + let z: PtrWrapper u32> = + coerce_ptr_wrapper_poly(PtrWrapper(2, 3, (), &square_local)); + assert_eq!((z.3)(6), 36); +} diff --git a/src/tools/miri/tests/pass/concurrency/channels.rs b/src/tools/miri/tests/pass/concurrency/channels.rs new file mode 100644 index 0000000000000..c75c5199bf11d --- /dev/null +++ b/src/tools/miri/tests/pass/concurrency/channels.rs @@ -0,0 +1,66 @@ +//@ignore-target-windows: Channels on Windows are not supported yet. +//@compile-flags: -Zmiri-strict-provenance + +use std::sync::mpsc::{channel, sync_channel}; +use std::thread; + +// Check if channels are working. + +/// The test taken from the Rust documentation. +fn simple_send() { + let (tx, rx) = channel(); + let t = thread::spawn(move || { + tx.send(10).unwrap(); + }); + assert_eq!(rx.recv().unwrap(), 10); + t.join().unwrap(); +} + +/// The test taken from the Rust documentation. +fn multiple_send() { + let (tx, rx) = channel(); + let mut threads = vec![]; + for i in 0..10 { + let tx = tx.clone(); + let t = thread::spawn(move || { + tx.send(i).unwrap(); + }); + threads.push(t); + } + + let mut sum = 0; + for _ in 0..10 { + let j = rx.recv().unwrap(); + assert!(0 <= j && j < 10); + sum += j; + } + assert_eq!(sum, 45); + + for t in threads { + t.join().unwrap(); + } +} + +/// The test taken from the Rust documentation. +fn send_on_sync() { + let (sender, receiver) = sync_channel(1); + + // this returns immediately + sender.send(1).unwrap(); + + let t = thread::spawn(move || { + // this will block until the previous message has been received + sender.send(2).unwrap(); + }); + + assert_eq!(receiver.recv().unwrap(), 1); + assert_eq!(receiver.recv().unwrap(), 2); + + t.join().unwrap(); +} + +fn main() { + simple_send(); + multiple_send(); + send_on_sync(); +} diff --git a/src/tools/miri/tests/pass/concurrency/concurrent_caller_location.rs b/src/tools/miri/tests/pass/concurrency/concurrent_caller_location.rs new file mode 100644 index 0000000000000..0490330a15d8b --- /dev/null +++ b/src/tools/miri/tests/pass/concurrency/concurrent_caller_location.rs @@ -0,0 +1,17 @@ +use std::panic::Location; +use std::thread::spawn; + +fn initialize() { + let _ignore = initialize_inner(); +} + +fn initialize_inner() -> &'static Location<'static> { + Location::caller() +} + +fn main() { + let j1 = spawn(initialize); + let j2 = spawn(initialize); + j1.join().unwrap(); + j2.join().unwrap(); +} diff --git a/src/tools/miri/tests/pass/concurrency/data_race.rs b/src/tools/miri/tests/pass/concurrency/data_race.rs new file mode 100644 index 0000000000000..4e3c99058a0df --- /dev/null +++ b/src/tools/miri/tests/pass/concurrency/data_race.rs @@ -0,0 +1,110 @@ +//@compile-flags: -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0 + +use std::sync::atomic::{fence, AtomicUsize, Ordering}; +use std::thread::spawn; + +#[derive(Copy, Clone)] +struct EvilSend(pub T); + +unsafe impl Send for EvilSend {} +unsafe impl Sync for EvilSend {} + +fn test_fence_sync() { + static SYNC: AtomicUsize = AtomicUsize::new(0); + + let mut var = 0u32; + let ptr = &mut var as *mut u32; + let evil_ptr = EvilSend(ptr); + + let j1 = spawn(move || { + unsafe { *evil_ptr.0 = 1 }; + fence(Ordering::Release); + SYNC.store(1, Ordering::Relaxed) + }); + + let j2 = spawn(move || { + if SYNC.load(Ordering::Relaxed) == 1 { + fence(Ordering::Acquire); + unsafe { *evil_ptr.0 } + } else { + panic!(); // relies on thread 2 going last + } + }); + + j1.join().unwrap(); + j2.join().unwrap(); +} + +fn test_multiple_reads() { + let mut var = 42u32; + let ptr = &mut var as *mut u32; + let evil_ptr = EvilSend(ptr); + + let j1 = spawn(move || unsafe { *evil_ptr.0 }); + let j2 = spawn(move || unsafe { *evil_ptr.0 }); + let j3 = spawn(move || unsafe { *evil_ptr.0 }); + let j4 = spawn(move || unsafe { *evil_ptr.0 }); + + assert_eq!(j1.join().unwrap(), 42); + assert_eq!(j2.join().unwrap(), 42); + assert_eq!(j3.join().unwrap(), 42); + assert_eq!(j4.join().unwrap(), 42); + + var = 10; + assert_eq!(var, 10); +} + +pub fn test_rmw_no_block() { + static SYNC: AtomicUsize = AtomicUsize::new(0); + + let mut a = 0u32; + let b = &mut a as *mut u32; + let c = EvilSend(b); + + unsafe { + let j1 = spawn(move || { + *c.0 = 1; + SYNC.store(1, Ordering::Release); + }); + + let j2 = spawn(move || { + if SYNC.swap(2, Ordering::Relaxed) == 1 { + //No op, blocking store removed + } + }); + + let j3 = spawn(move || if SYNC.load(Ordering::Acquire) == 2 { *c.0 } else { 0 }); + + j1.join().unwrap(); + j2.join().unwrap(); + let v = j3.join().unwrap(); + assert!(v == 1 || v == 2); // relies on thread 3 going last + } +} + +pub fn test_simple_release() { + static SYNC: AtomicUsize = AtomicUsize::new(0); + + let mut a = 0u32; + let b = &mut a as *mut u32; + let c = EvilSend(b); + + unsafe { + let j1 = spawn(move || { + *c.0 = 1; + SYNC.store(1, Ordering::Release); + }); + + let j2 = spawn(move || if SYNC.load(Ordering::Acquire) == 1 { *c.0 } else { 0 }); + + j1.join().unwrap(); + assert_eq!(j2.join().unwrap(), 1); // relies on thread 2 going last + } +} + +pub fn main() { + test_fence_sync(); + test_multiple_reads(); + test_rmw_no_block(); + test_simple_release(); +} diff --git a/src/tools/miri/tests/pass/concurrency/disable_data_race_detector.rs b/src/tools/miri/tests/pass/concurrency/disable_data_race_detector.rs new file mode 100644 index 0000000000000..d71e51b038429 --- /dev/null +++ b/src/tools/miri/tests/pass/concurrency/disable_data_race_detector.rs @@ -0,0 +1,27 @@ +//@compile-flags: -Zmiri-disable-data-race-detector + +use std::thread::spawn; + +#[derive(Copy, Clone)] +struct EvilSend(pub T); + +unsafe impl Send for EvilSend {} +unsafe impl Sync for EvilSend {} + +pub fn main() { + let mut a = 0u32; + let b = &mut a as *mut u32; + let c = EvilSend(b); + unsafe { + let j1 = spawn(move || { + *c.0 = 32; + }); + + let j2 = spawn(move || { + *c.0 = 64; // Data race (but not detected as the detector is disabled) + }); + + j1.join().unwrap(); + j2.join().unwrap(); + } +} diff --git a/src/tools/miri/tests/pass/concurrency/issue1643.rs b/src/tools/miri/tests/pass/concurrency/issue1643.rs new file mode 100644 index 0000000000000..c0956569ad8f9 --- /dev/null +++ b/src/tools/miri/tests/pass/concurrency/issue1643.rs @@ -0,0 +1,14 @@ +use std::thread::spawn; + +fn initialize() { + initialize_inner(&mut || false) +} + +fn initialize_inner(_init: &mut dyn FnMut() -> bool) {} + +fn main() { + let j1 = spawn(initialize); + let j2 = spawn(initialize); + j1.join().unwrap(); + j2.join().unwrap(); +} diff --git a/src/tools/miri/tests/pass/concurrency/mutex_leak.rs b/src/tools/miri/tests/pass/concurrency/mutex_leak.rs new file mode 100644 index 0000000000000..3ac0c9336b7b1 --- /dev/null +++ b/src/tools/miri/tests/pass/concurrency/mutex_leak.rs @@ -0,0 +1,9 @@ +//@compile-flags: -Zmiri-ignore-leaks +use std::mem; +use std::sync::Mutex; + +fn main() { + // Test for https://github.com/rust-lang/rust/issues/85434 + let m = Mutex::new(5i32); + mem::forget(m.lock()); +} diff --git a/src/tools/miri/tests/pass/concurrency/simple.rs b/src/tools/miri/tests/pass/concurrency/simple.rs new file mode 100644 index 0000000000000..556e0a24769d7 --- /dev/null +++ b/src/tools/miri/tests/pass/concurrency/simple.rs @@ -0,0 +1,76 @@ +//@compile-flags: -Zmiri-strict-provenance + +use std::thread; + +fn create_and_detach() { + thread::spawn(|| ()); +} + +fn create_and_join() { + thread::spawn(|| ()).join().unwrap(); +} + +fn create_and_get_result() { + let nine = thread::spawn(|| 5 + 4).join().unwrap(); + assert_eq!(nine, 9); +} + +fn create_and_leak_result() { + thread::spawn(|| 7); +} + +fn create_nested_and_detach() { + thread::spawn(|| { + thread::spawn(|| ()); + }); +} + +fn create_nested_and_join() { + let handle = thread::spawn(|| thread::spawn(|| ())); + let handle_nested = handle.join().unwrap(); + handle_nested.join().unwrap(); +} + +fn create_move_in() { + let x = String::from("Hello!"); + thread::spawn(move || { + assert_eq!(x.len(), 6); + }) + .join() + .unwrap(); +} + +fn create_move_out() { + let result = thread::spawn(|| String::from("Hello!")).join().unwrap(); + assert_eq!(result.len(), 6); +} + +fn panic() { + let result = thread::spawn(|| panic!("Hello!")).join().unwrap_err(); + let msg = result.downcast_ref::<&'static str>().unwrap(); + assert_eq!(*msg, "Hello!"); +} + +fn panic_named() { + thread::Builder::new() + .name("childthread".to_string()) + .spawn(move || { + panic!("Hello, world!"); + }) + .unwrap() + .join() + .unwrap_err(); +} + +fn main() { + create_and_detach(); + create_and_join(); + create_and_get_result(); + create_and_leak_result(); + create_nested_and_detach(); + create_nested_and_join(); + create_move_in(); + create_move_out(); + panic(); + panic_named(); +} diff --git a/src/tools/miri/tests/pass/concurrency/simple.stderr b/src/tools/miri/tests/pass/concurrency/simple.stderr new file mode 100644 index 0000000000000..028cc0fb736ff --- /dev/null +++ b/src/tools/miri/tests/pass/concurrency/simple.stderr @@ -0,0 +1,3 @@ +thread '' panicked at 'Hello!', $DIR/simple.rs:LL:CC +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +thread 'childthread' panicked at 'Hello, world!', $DIR/simple.rs:LL:CC diff --git a/src/tools/miri/tests/pass/concurrency/spin_loop.rs b/src/tools/miri/tests/pass/concurrency/spin_loop.rs new file mode 100644 index 0000000000000..019bd44f16488 --- /dev/null +++ b/src/tools/miri/tests/pass/concurrency/spin_loop.rs @@ -0,0 +1,42 @@ +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::thread; + +static FLAG: AtomicUsize = AtomicUsize::new(0); + +fn spin() { + let j = thread::spawn(|| { + while FLAG.load(Ordering::Acquire) == 0 { + // We do *not* yield, and yet this should terminate eventually. + } + }); + thread::yield_now(); // schedule the other thread + FLAG.store(1, Ordering::Release); + j.join().unwrap(); +} + +fn two_player_ping_pong() { + static FLAG: AtomicUsize = AtomicUsize::new(0); + + let waiter1 = thread::spawn(|| { + while FLAG.load(Ordering::Acquire) == 0 { + // We do *not* yield, and yet this should terminate eventually. + } + }); + let waiter2 = thread::spawn(|| { + while FLAG.load(Ordering::Acquire) == 0 { + // We do *not* yield, and yet this should terminate eventually. + } + }); + let progress = thread::spawn(|| { + FLAG.store(1, Ordering::Release); + }); + // The first `join` blocks the main thread and thus takes it out of the equation. + waiter1.join().unwrap(); + waiter2.join().unwrap(); + progress.join().unwrap(); +} + +fn main() { + spin(); + two_player_ping_pong(); +} diff --git a/src/tools/miri/tests/pass/concurrency/spin_loops.stderr b/src/tools/miri/tests/pass/concurrency/spin_loops.stderr new file mode 100644 index 0000000000000..9fe6daa778c1f --- /dev/null +++ b/src/tools/miri/tests/pass/concurrency/spin_loops.stderr @@ -0,0 +1,3 @@ +warning: thread support is experimental: the scheduler is not preemptive, and can get stuck in spin loops. + (see https://github.com/rust-lang/miri/issues/1388) + diff --git a/src/tools/miri/tests/pass/concurrency/spin_loops_nopreempt.rs b/src/tools/miri/tests/pass/concurrency/spin_loops_nopreempt.rs new file mode 100644 index 0000000000000..5d8e2ef5f0282 --- /dev/null +++ b/src/tools/miri/tests/pass/concurrency/spin_loops_nopreempt.rs @@ -0,0 +1,84 @@ +//@ignore-target-windows: Channels on Windows are not supported yet. +// This specifically tests behavior *without* preemption. +//@compile-flags: -Zmiri-preemption-rate=0 + +use std::cell::Cell; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use std::sync::mpsc; +use std::thread; + +/// When a thread yields, Miri's scheduler used to pick the thread with the lowest ID +/// that can run. IDs are assigned in thread creation order. +/// This means we could make 2 threads infinitely ping-pong with each other while +/// really there is a 3rd thread that we should schedule to make progress. +fn two_player_ping_pong() { + static FLAG: AtomicUsize = AtomicUsize::new(0); + + let waiter1 = thread::spawn(|| { + while FLAG.load(Ordering::Acquire) == 0 { + // spin and wait + thread::yield_now(); + } + }); + let waiter2 = thread::spawn(|| { + while FLAG.load(Ordering::Acquire) == 0 { + // spin and wait + thread::yield_now(); + } + }); + let progress = thread::spawn(|| { + FLAG.store(1, Ordering::Release); + }); + // The first `join` blocks the main thread and thus takes it out of the equation. + waiter1.join().unwrap(); + waiter2.join().unwrap(); + progress.join().unwrap(); +} + +/// Based on a test by @jethrogb. +fn launcher() { + static THREAD2_LAUNCHED: AtomicBool = AtomicBool::new(false); + + for _ in 0..10 { + let (tx, rx) = mpsc::sync_channel(0); + THREAD2_LAUNCHED.store(false, Ordering::SeqCst); + + let jh = thread::spawn(move || { + struct RecvOnDrop(Cell>>); + + impl Drop for RecvOnDrop { + fn drop(&mut self) { + let rx = self.0.take().unwrap(); + while !THREAD2_LAUNCHED.load(Ordering::SeqCst) { + thread::yield_now(); + } + rx.recv().unwrap(); + } + } + + let tl_rx: RecvOnDrop = RecvOnDrop(Cell::new(None)); + tl_rx.0.set(Some(rx)); + }); + + let tx_clone = tx.clone(); + let jh2 = thread::spawn(move || { + THREAD2_LAUNCHED.store(true, Ordering::SeqCst); + jh.join().unwrap(); + tx_clone.send(()).expect_err( + "Expecting channel to be closed because thread 1 TLS destructors must've run", + ); + }); + + while !THREAD2_LAUNCHED.load(Ordering::SeqCst) { + thread::yield_now(); + } + thread::yield_now(); + tx.send(()).expect("Expecting channel to be live because thread 2 must block on join"); + jh2.join().unwrap(); + } +} + +fn main() { + two_player_ping_pong(); + launcher(); +} diff --git a/src/tools/miri/tests/pass/concurrency/sync.rs b/src/tools/miri/tests/pass/concurrency/sync.rs new file mode 100644 index 0000000000000..8bda32bb95a48 --- /dev/null +++ b/src/tools/miri/tests/pass/concurrency/sync.rs @@ -0,0 +1,238 @@ +//@ignore-target-windows: Concurrency on Windows is not supported yet. +//@compile-flags: -Zmiri-disable-isolation -Zmiri-strict-provenance + +use std::sync::{Arc, Barrier, Condvar, Mutex, Once, RwLock}; +use std::thread; +use std::time::{Duration, Instant}; + +// Check if Rust barriers are working. + +/// This test is taken from the Rust documentation. +fn check_barriers() { + let mut handles = Vec::with_capacity(10); + let barrier = Arc::new(Barrier::new(10)); + for _ in 0..10 { + let c = barrier.clone(); + // The same messages will be printed together. + // You will NOT see any interleaving. + handles.push(thread::spawn(move || { + println!("before wait"); + c.wait(); + println!("after wait"); + })); + } + // Wait for other threads to finish. + for handle in handles { + handle.join().unwrap(); + } +} + +// Check if Rust conditional variables are working. + +/// The test taken from the Rust documentation. +fn check_conditional_variables_notify_one() { + let pair = Arc::new((Mutex::new(false), Condvar::new())); + let pair2 = pair.clone(); + + // Spawn a new thread. + let t = thread::spawn(move || { + thread::yield_now(); + let (lock, cvar) = &*pair2; + let mut started = lock.lock().unwrap(); + *started = true; + // We notify the condvar that the value has changed. + cvar.notify_one(); + }); + + // Wait for the thread to fully start up. + let (lock, cvar) = &*pair; + let mut started = lock.lock().unwrap(); + while !*started { + started = cvar.wait(started).unwrap(); + } + + t.join().unwrap(); +} + +/// Test that waiting on a conditional variable with a timeout does not +/// deadlock. +fn check_conditional_variables_timed_wait_timeout() { + let lock = Mutex::new(()); + let cvar = Condvar::new(); + let guard = lock.lock().unwrap(); + let now = Instant::now(); + let (_guard, timeout) = cvar.wait_timeout(guard, Duration::from_millis(100)).unwrap(); + assert!(timeout.timed_out()); + let elapsed_time = now.elapsed().as_millis(); + assert!(100 <= elapsed_time && elapsed_time <= 1000); +} + +/// Test that signaling a conditional variable when waiting with a timeout works +/// as expected. +fn check_conditional_variables_timed_wait_notimeout() { + let pair = Arc::new((Mutex::new(()), Condvar::new())); + let pair2 = pair.clone(); + + let (lock, cvar) = &*pair; + let guard = lock.lock().unwrap(); + + let handle = thread::spawn(move || { + thread::sleep(Duration::from_millis(100)); // Make sure the other thread is waiting by the time we call `notify`. + let (_lock, cvar) = &*pair2; + cvar.notify_one(); + }); + + let (_guard, timeout) = cvar.wait_timeout(guard, Duration::from_millis(500)).unwrap(); + assert!(!timeout.timed_out()); + handle.join().unwrap(); +} + +// Check if locks are working. + +fn check_mutex() { + let data = Arc::new(Mutex::new(0)); + let mut threads = Vec::new(); + + for _ in 0..3 { + let data = Arc::clone(&data); + let thread = thread::spawn(move || { + let mut data = data.lock().unwrap(); + thread::yield_now(); + *data += 1; + }); + threads.push(thread); + } + + for thread in threads { + thread.join().unwrap(); + } + + assert!(data.try_lock().is_ok()); + + let data = Arc::try_unwrap(data).unwrap().into_inner().unwrap(); + assert_eq!(data, 3); +} + +fn check_rwlock_write() { + let data = Arc::new(RwLock::new(0)); + let mut threads = Vec::new(); + + for _ in 0..3 { + let data = Arc::clone(&data); + let thread = thread::spawn(move || { + let mut data = data.write().unwrap(); + thread::yield_now(); + *data += 1; + }); + threads.push(thread); + } + + for thread in threads { + thread.join().unwrap(); + } + + assert!(data.try_write().is_ok()); + + let data = Arc::try_unwrap(data).unwrap().into_inner().unwrap(); + assert_eq!(data, 3); +} + +fn check_rwlock_read_no_deadlock() { + let l1 = Arc::new(RwLock::new(0)); + let l2 = Arc::new(RwLock::new(0)); + + let l1_copy = Arc::clone(&l1); + let l2_copy = Arc::clone(&l2); + let _guard1 = l1.read().unwrap(); + let handle = thread::spawn(move || { + let _guard2 = l2_copy.read().unwrap(); + thread::yield_now(); + let _guard1 = l1_copy.read().unwrap(); + }); + thread::yield_now(); + let _guard2 = l2.read().unwrap(); + handle.join().unwrap(); +} + +// Check if Rust once statics are working. + +static mut VAL: usize = 0; +static INIT: Once = Once::new(); + +fn get_cached_val() -> usize { + unsafe { + INIT.call_once(|| { + VAL = expensive_computation(); + }); + VAL + } +} + +fn expensive_computation() -> usize { + let mut i = 1; + let mut c = 1; + while i < 1000 { + i *= c; + c += 1; + } + i +} + +/// The test taken from the Rust documentation. +fn check_once() { + let handles: Vec<_> = (0..10) + .map(|_| { + thread::spawn(|| { + thread::yield_now(); + let val = get_cached_val(); + assert_eq!(val, 5040); + }) + }) + .collect(); + for handle in handles { + handle.join().unwrap(); + } +} + +fn park_timeout() { + let start = Instant::now(); + + thread::park_timeout(Duration::from_millis(200)); + // Normally, waiting in park/park_timeout may spuriously wake up early, but we + // know Miri's timed synchronization primitives do not do that. + + assert!((200..1000).contains(&start.elapsed().as_millis())); +} + +fn park_unpark() { + let t1 = thread::current(); + let t2 = thread::spawn(move || { + thread::park(); + thread::sleep(Duration::from_millis(200)); + t1.unpark(); + }); + + let start = Instant::now(); + + t2.thread().unpark(); + thread::park(); + // Normally, waiting in park/park_timeout may spuriously wake up early, but we + // know Miri's timed synchronization primitives do not do that. + + assert!((200..1000).contains(&start.elapsed().as_millis())); + + t2.join().unwrap(); +} + +fn main() { + check_barriers(); + check_conditional_variables_notify_one(); + check_conditional_variables_timed_wait_timeout(); + check_conditional_variables_timed_wait_notimeout(); + check_mutex(); + check_rwlock_write(); + check_rwlock_read_no_deadlock(); + check_once(); + park_timeout(); + park_unpark(); +} diff --git a/src/tools/miri/tests/pass/concurrency/sync.stdout b/src/tools/miri/tests/pass/concurrency/sync.stdout new file mode 100644 index 0000000000000..f2c036a1735ed --- /dev/null +++ b/src/tools/miri/tests/pass/concurrency/sync.stdout @@ -0,0 +1,20 @@ +before wait +before wait +before wait +before wait +before wait +before wait +before wait +before wait +before wait +before wait +after wait +after wait +after wait +after wait +after wait +after wait +after wait +after wait +after wait +after wait diff --git a/src/tools/miri/tests/pass/concurrency/sync_nopreempt.rs b/src/tools/miri/tests/pass/concurrency/sync_nopreempt.rs new file mode 100644 index 0000000000000..3da33fee4c0ed --- /dev/null +++ b/src/tools/miri/tests/pass/concurrency/sync_nopreempt.rs @@ -0,0 +1,88 @@ +//@ignore-target-windows: Concurrency on Windows is not supported yet. +// We are making scheduler assumptions here. +//@compile-flags: -Zmiri-strict-provenance -Zmiri-preemption-rate=0 + +use std::sync::{Arc, Condvar, Mutex, RwLock}; +use std::thread; + +fn check_conditional_variables_notify_all() { + let pair = Arc::new(((Mutex::new(())), Condvar::new())); + + // Spawn threads and block them on the conditional variable. + let handles: Vec<_> = (0..5) + .map(|_| { + let pair2 = pair.clone(); + thread::spawn(move || { + let (lock, cvar) = &*pair2; + let guard = lock.lock().unwrap(); + // Block waiting on the conditional variable. + let _guard = cvar.wait(guard).unwrap(); + }) + }) + .inspect(|_| { + // Ensure the other threads all run and block on the `wait`. + thread::yield_now(); + thread::yield_now(); + }) + .collect(); + + let (_, cvar) = &*pair; + // Unblock all threads. + cvar.notify_all(); + + for handle in handles { + handle.join().unwrap(); + } +} + +fn check_rwlock_unlock_bug1() { + // There was a bug where when un-read-locking an rwlock that still has other + // readers waiting, we'd accidentally also let a writer in. + // That caused an ICE. + let l = Arc::new(RwLock::new(0)); + + let r1 = l.read().unwrap(); + let r2 = l.read().unwrap(); + + // Make a waiting writer. + let l2 = l.clone(); + let t = thread::spawn(move || { + let mut w = l2.write().unwrap(); + *w += 1; + }); + thread::yield_now(); + + drop(r1); + assert_eq!(*r2, 0); + thread::yield_now(); + thread::yield_now(); + thread::yield_now(); + assert_eq!(*r2, 0); + drop(r2); + t.join().unwrap(); +} + +fn check_rwlock_unlock_bug2() { + // There was a bug where when un-read-locking an rwlock by letting the last reader leaver, + // we'd forget to wake up a writer. + // That meant the writer thread could never run again. + let l = Arc::new(RwLock::new(0)); + + let r = l.read().unwrap(); + + // Make a waiting writer. + let l2 = l.clone(); + let h = thread::spawn(move || { + let _w = l2.write().unwrap(); + }); + thread::yield_now(); + + drop(r); + h.join().unwrap(); +} + +fn main() { + check_conditional_variables_notify_all(); + check_rwlock_unlock_bug1(); + check_rwlock_unlock_bug2(); +} diff --git a/src/tools/miri/tests/pass/concurrency/sync_singlethread.rs b/src/tools/miri/tests/pass/concurrency/sync_singlethread.rs new file mode 100644 index 0000000000000..5663e1c1426c9 --- /dev/null +++ b/src/tools/miri/tests/pass/concurrency/sync_singlethread.rs @@ -0,0 +1,60 @@ +use std::hint; +use std::sync::atomic; +use std::sync::{Mutex, TryLockError}; + +fn main() { + test_mutex_stdlib(); + test_rwlock_stdlib(); + test_spin_loop_hint(); + test_thread_yield_now(); +} + +fn test_mutex_stdlib() { + let m = Mutex::new(0); + { + let _guard = m.lock(); + assert!(m.try_lock().unwrap_err().would_block()); + } + drop(m.try_lock().unwrap()); + drop(m); +} + +fn test_rwlock_stdlib() { + use std::sync::RwLock; + let rw = RwLock::new(0); + { + let _read_guard = rw.read().unwrap(); + drop(rw.read().unwrap()); + drop(rw.try_read().unwrap()); + assert!(rw.try_write().unwrap_err().would_block()); + } + + { + let _write_guard = rw.write().unwrap(); + assert!(rw.try_read().unwrap_err().would_block()); + assert!(rw.try_write().unwrap_err().would_block()); + } +} + +trait TryLockErrorExt { + fn would_block(&self) -> bool; +} + +impl TryLockErrorExt for TryLockError { + fn would_block(&self) -> bool { + match self { + TryLockError::WouldBlock => true, + TryLockError::Poisoned(_) => false, + } + } +} + +fn test_spin_loop_hint() { + #[allow(deprecated)] + atomic::spin_loop_hint(); + hint::spin_loop(); +} + +fn test_thread_yield_now() { + std::thread::yield_now(); +} diff --git a/src/tools/miri/tests/pass/concurrency/thread_locals.rs b/src/tools/miri/tests/pass/concurrency/thread_locals.rs new file mode 100644 index 0000000000000..b19e56312f304 --- /dev/null +++ b/src/tools/miri/tests/pass/concurrency/thread_locals.rs @@ -0,0 +1,65 @@ +//@compile-flags: -Zmiri-strict-provenance + +//! The main purpose of this test is to check that if we take a pointer to +//! thread's `t1` thread-local `A` and send it to another thread `t2`, +//! dereferencing the pointer on `t2` resolves to `t1`'s thread-local. In this +//! test, we also check that thread-locals act as per-thread statics. + +#![feature(thread_local)] + +use std::thread; + +#[thread_local] +static mut A: u8 = 0; +#[thread_local] +static mut B: u8 = 0; +static mut C: u8 = 0; + +// Regression test for https://github.com/rust-lang/rust/issues/96191. +#[thread_local] +static READ_ONLY: u8 = 42; + +unsafe fn get_a_ref() -> *mut u8 { + &mut A +} + +struct Sender(*mut u8); + +unsafe impl Send for Sender {} + +fn main() { + let _val = READ_ONLY; + + let ptr = unsafe { + let x = get_a_ref(); + *x = 5; + assert_eq!(A, 5); + B = 15; + C = 25; + Sender(&mut A) + }; + + thread::spawn(move || unsafe { + assert_eq!(*ptr.0, 5); + assert_eq!(A, 0); + assert_eq!(B, 0); + assert_eq!(C, 25); + B = 14; + C = 24; + let y = get_a_ref(); + assert_eq!(*y, 0); + *y = 4; + assert_eq!(*ptr.0, 5); + assert_eq!(A, 4); + assert_eq!(*get_a_ref(), 4); + }) + .join() + .unwrap(); + + unsafe { + assert_eq!(*get_a_ref(), 5); + assert_eq!(A, 5); + assert_eq!(B, 15); + assert_eq!(C, 24); + } +} diff --git a/src/tools/miri/tests/pass/concurrency/tls_lib_drop.rs b/src/tools/miri/tests/pass/concurrency/tls_lib_drop.rs new file mode 100644 index 0000000000000..04e89ec361b74 --- /dev/null +++ b/src/tools/miri/tests/pass/concurrency/tls_lib_drop.rs @@ -0,0 +1,193 @@ +use std::cell::RefCell; +use std::thread; + +struct TestCell { + value: RefCell, +} + +impl Drop for TestCell { + fn drop(&mut self) { + for _ in 0..10 { + thread::yield_now(); + } + println!("Dropping: {} (should be before 'Continue main 1').", *self.value.borrow()) + } +} + +thread_local! { + static A: TestCell = TestCell { value: RefCell::new(0) }; + static A_CONST: TestCell = const { TestCell { value: RefCell::new(10) } }; +} + +/// Check that destructors of the library thread locals are executed immediately +/// after a thread terminates. +fn check_destructors() { + // We use the same value for both of them, since destructor order differs between Miri on Linux + // (which uses `register_dtor_fallback`, in the end using a single pthread_key to manage a + // thread-local linked list of dtors to call), real Linux rustc (which uses + // `__cxa_thread_atexit_impl`), and Miri on Windows. + thread::spawn(|| { + A.with(|f| { + assert_eq!(*f.value.borrow(), 0); + *f.value.borrow_mut() = 8; + }); + A_CONST.with(|f| { + assert_eq!(*f.value.borrow(), 10); + *f.value.borrow_mut() = 8; + }); + }) + .join() + .unwrap(); + println!("Continue main 1.") +} + +struct JoinCell { + value: RefCell>>, +} + +impl Drop for JoinCell { + fn drop(&mut self) { + for _ in 0..10 { + thread::yield_now(); + } + let join_handle = self.value.borrow_mut().take().unwrap(); + println!("Joining: {} (should be before 'Continue main 2').", join_handle.join().unwrap()); + } +} + +thread_local! { + static B: JoinCell = JoinCell { value: RefCell::new(None) }; +} + +/// Check that the destructor can be blocked joining another thread. +fn check_blocking() { + thread::spawn(|| { + B.with(|f| { + assert!(f.value.borrow().is_none()); + let handle = thread::spawn(|| 7); + *f.value.borrow_mut() = Some(handle); + }); + }) + .join() + .unwrap(); + println!("Continue main 2."); + // Preempt the main thread so that the destructor gets executed and can join + // the thread. + thread::yield_now(); + thread::yield_now(); +} + +// This test tests that TLS destructors have run before the thread joins. The +// test has no false positives (meaning: if the test fails, there's actually +// an ordering problem). It may have false negatives, where the test passes but +// join is not guaranteed to be after the TLS destructors. However, false +// negatives should be exceedingly rare due to judicious use of +// thread::yield_now and running the test several times. +fn join_orders_after_tls_destructors() { + use std::sync::atomic::{AtomicU8, Ordering}; + + // We emulate a synchronous MPSC rendezvous channel using only atomics and + // thread::yield_now. We can't use std::mpsc as the implementation itself + // may rely on thread locals. + // + // The basic state machine for an SPSC rendezvous channel is: + // FRESH -> THREAD1_WAITING -> MAIN_THREAD_RENDEZVOUS + // where the first transition is done by the “receiving” thread and the 2nd + // transition is done by the “sending” thread. + // + // We add an additional state `THREAD2_LAUNCHED` between `FRESH` and + // `THREAD1_WAITING` to block until all threads are actually running. + // + // A thread that joins on the “receiving” thread completion should never + // observe the channel in the `THREAD1_WAITING` state. If this does occur, + // we switch to the “poison” state `THREAD2_JOINED` and panic all around. + // (This is equivalent to “sending” from an alternate producer thread.) + const FRESH: u8 = 0; + const THREAD2_LAUNCHED: u8 = 1; + const THREAD1_WAITING: u8 = 2; + const MAIN_THREAD_RENDEZVOUS: u8 = 3; + const THREAD2_JOINED: u8 = 4; + static SYNC_STATE: AtomicU8 = AtomicU8::new(FRESH); + + for _ in 0..10 { + SYNC_STATE.store(FRESH, Ordering::SeqCst); + + let jh = thread::Builder::new() + .name("thread1".into()) + .spawn(move || { + struct TlDrop; + + impl Drop for TlDrop { + fn drop(&mut self) { + let mut sync_state = SYNC_STATE.swap(THREAD1_WAITING, Ordering::SeqCst); + loop { + match sync_state { + THREAD2_LAUNCHED | THREAD1_WAITING => thread::yield_now(), + MAIN_THREAD_RENDEZVOUS => break, + THREAD2_JOINED => + panic!( + "Thread 1 still running after thread 2 joined on thread 1" + ), + v => unreachable!("sync state: {}", v), + } + sync_state = SYNC_STATE.load(Ordering::SeqCst); + } + } + } + + thread_local! { + static TL_DROP: TlDrop = TlDrop; + } + + TL_DROP.with(|_| {}); + + loop { + match SYNC_STATE.load(Ordering::SeqCst) { + FRESH => thread::yield_now(), + THREAD2_LAUNCHED => break, + v => unreachable!("sync state: {}", v), + } + } + }) + .unwrap(); + + let jh2 = thread::Builder::new() + .name("thread2".into()) + .spawn(move || { + assert_eq!(SYNC_STATE.swap(THREAD2_LAUNCHED, Ordering::SeqCst), FRESH); + jh.join().unwrap(); + match SYNC_STATE.swap(THREAD2_JOINED, Ordering::SeqCst) { + MAIN_THREAD_RENDEZVOUS => return, + THREAD2_LAUNCHED | THREAD1_WAITING => { + panic!("Thread 2 running after thread 1 join before main thread rendezvous") + } + v => unreachable!("sync state: {:?}", v), + } + }) + .unwrap(); + + loop { + match SYNC_STATE.compare_exchange( + THREAD1_WAITING, + MAIN_THREAD_RENDEZVOUS, + Ordering::SeqCst, + Ordering::SeqCst, + ) { + Ok(_) => break, + Err(FRESH) => thread::yield_now(), + Err(THREAD2_LAUNCHED) => thread::yield_now(), + Err(THREAD2_JOINED) => { + panic!("Main thread rendezvous after thread 2 joined thread 1") + } + v => unreachable!("sync state: {:?}", v), + } + } + jh2.join().unwrap(); + } +} + +fn main() { + check_destructors(); + check_blocking(); + join_orders_after_tls_destructors(); +} diff --git a/src/tools/miri/tests/pass/concurrency/tls_lib_drop.stdout b/src/tools/miri/tests/pass/concurrency/tls_lib_drop.stdout new file mode 100644 index 0000000000000..b7877820a0ca9 --- /dev/null +++ b/src/tools/miri/tests/pass/concurrency/tls_lib_drop.stdout @@ -0,0 +1,5 @@ +Dropping: 8 (should be before 'Continue main 1'). +Dropping: 8 (should be before 'Continue main 1'). +Continue main 1. +Joining: 7 (should be before 'Continue main 2'). +Continue main 2. diff --git a/src/tools/miri/tests/pass/concurrency/tls_lib_drop_single_thread.rs b/src/tools/miri/tests/pass/concurrency/tls_lib_drop_single_thread.rs new file mode 100644 index 0000000000000..2766ba36d12b6 --- /dev/null +++ b/src/tools/miri/tests/pass/concurrency/tls_lib_drop_single_thread.rs @@ -0,0 +1,30 @@ +//! Check that destructors of the thread locals are executed on all OSes. + +use std::cell::RefCell; + +struct TestCell { + value: RefCell, +} + +impl Drop for TestCell { + fn drop(&mut self) { + eprintln!("Dropping: {}", *self.value.borrow()) + } +} + +thread_local! { + static A: TestCell = TestCell { value: RefCell::new(0) }; + static A_CONST: TestCell = const { TestCell { value: RefCell::new(10) } }; +} + +fn main() { + A.with(|f| { + assert_eq!(*f.value.borrow(), 0); + *f.value.borrow_mut() = 5; + }); + A_CONST.with(|f| { + assert_eq!(*f.value.borrow(), 10); + *f.value.borrow_mut() = 5; // Same value as above since the drop order is different on different platforms + }); + eprintln!("Continue main.") +} diff --git a/src/tools/miri/tests/pass/concurrency/tls_lib_drop_single_thread.stderr b/src/tools/miri/tests/pass/concurrency/tls_lib_drop_single_thread.stderr new file mode 100644 index 0000000000000..09ec1c3c2c511 --- /dev/null +++ b/src/tools/miri/tests/pass/concurrency/tls_lib_drop_single_thread.stderr @@ -0,0 +1,3 @@ +Continue main. +Dropping: 5 +Dropping: 5 diff --git a/src/tools/miri/tests/pass/concurrency/windows_detach_terminated.rs b/src/tools/miri/tests/pass/concurrency/windows_detach_terminated.rs new file mode 100644 index 0000000000000..91088ce6aef9b --- /dev/null +++ b/src/tools/miri/tests/pass/concurrency/windows_detach_terminated.rs @@ -0,0 +1,21 @@ +//@only-target-windows: Uses win32 api functions +// We are making scheduler assumptions here. +//@compile-flags: -Zmiri-preemption-rate=0 + +use std::os::windows::io::IntoRawHandle; +use std::thread; + +extern "system" { + fn CloseHandle(handle: usize) -> i32; +} + +fn main() { + let thread = thread::spawn(|| {}).into_raw_handle() as usize; + + // this yield ensures that `thread` is terminated by this point + thread::yield_now(); + + unsafe { + assert_ne!(CloseHandle(thread), 0); + } +} diff --git a/src/tools/miri/tests/pass/concurrency/windows_join_multiple.rs b/src/tools/miri/tests/pass/concurrency/windows_join_multiple.rs new file mode 100644 index 0000000000000..986e2b8cc10f7 --- /dev/null +++ b/src/tools/miri/tests/pass/concurrency/windows_join_multiple.rs @@ -0,0 +1,41 @@ +//@only-target-windows: Uses win32 api functions +// We are making scheduler assumptions here. +//@compile-flags: -Zmiri-preemption-rate=0 + +use std::os::windows::io::IntoRawHandle; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::thread; + +extern "system" { + fn WaitForSingleObject(handle: usize, timeout: u32) -> u32; +} + +const INFINITE: u32 = u32::MAX; + +fn main() { + static FLAG: AtomicBool = AtomicBool::new(false); + + let blocker = thread::spawn(|| { + while !FLAG.load(Ordering::Relaxed) { + thread::yield_now(); + } + }) + .into_raw_handle() as usize; + + let waiter = move || { + unsafe { + assert_eq!(WaitForSingleObject(blocker, INFINITE), 0); + } + }; + + let waiter1 = thread::spawn(waiter); + let waiter2 = thread::spawn(waiter); + + // this yield ensures `waiter1` & `waiter2` are blocked on `blocker` by this point + thread::yield_now(); + + FLAG.store(true, Ordering::Relaxed); + + waiter1.join().unwrap(); + waiter2.join().unwrap(); +} diff --git a/src/tools/miri/tests/pass/const-vec-of-fns.rs b/src/tools/miri/tests/pass/const-vec-of-fns.rs new file mode 100644 index 0000000000000..7f0782fe32247 --- /dev/null +++ b/src/tools/miri/tests/pass/const-vec-of-fns.rs @@ -0,0 +1,17 @@ +/*! + * Try to double-check that static fns have the right size (with or + * without dummy env ptr, as appropriate) by iterating a size-2 array. + * If the static size differs from the runtime size, the second element + * should be read as a null or otherwise wrong pointer and crash. + */ + +fn f() {} +static mut CLOSURES: &'static mut [fn()] = &mut [f as fn(), f as fn()]; + +pub fn main() { + unsafe { + for closure in &mut *CLOSURES { + (*closure)() + } + } +} diff --git a/src/tools/miri/tests/pass/constants.rs b/src/tools/miri/tests/pass/constants.rs new file mode 100644 index 0000000000000..718c852601420 --- /dev/null +++ b/src/tools/miri/tests/pass/constants.rs @@ -0,0 +1,9 @@ +const A: usize = *&5; + +fn foo() -> usize { + A +} + +fn main() { + assert_eq!(foo(), A); +} diff --git a/src/tools/miri/tests/pass/deriving-associated-types.rs b/src/tools/miri/tests/pass/deriving-associated-types.rs new file mode 100644 index 0000000000000..4803792a97cee --- /dev/null +++ b/src/tools/miri/tests/pass/deriving-associated-types.rs @@ -0,0 +1,159 @@ +pub trait DeclaredTrait { + type Type; +} + +impl DeclaredTrait for i32 { + type Type = i32; +} + +pub trait WhereTrait { + type Type; +} + +impl WhereTrait for i32 { + type Type = i32; +} + +// Make sure we don't add a bound that just shares a name with an associated +// type. +pub mod module { + pub type Type = i32; +} + +#[derive(PartialEq, Debug)] +struct PrivateStruct(T); + +#[derive(PartialEq, Debug)] +struct TupleStruct( + module::Type, + Option, + A, + PrivateStruct, + B, + B::Type, + Option, + ::Type, + Option<::Type>, + C, + C::Type, + Option, + ::Type, + Option<::Type>, + ::Type, +) +where + C: WhereTrait; + +#[derive(PartialEq, Debug)] +pub struct Struct +where + C: WhereTrait, +{ + m1: module::Type, + m2: Option, + a1: A, + a2: PrivateStruct, + b: B, + b1: B::Type, + b2: Option, + b3: ::Type, + b4: Option<::Type>, + c: C, + c1: C::Type, + c2: Option, + c3: ::Type, + c4: Option<::Type>, + d: ::Type, +} + +#[derive(PartialEq, Debug)] +enum Enum +where + C: WhereTrait, +{ + Unit, + Seq( + module::Type, + Option, + A, + PrivateStruct, + B, + B::Type, + Option, + ::Type, + Option<::Type>, + C, + C::Type, + Option, + ::Type, + Option<::Type>, + ::Type, + ), + Map { + m1: module::Type, + m2: Option, + a1: A, + a2: PrivateStruct, + b: B, + b1: B::Type, + b2: Option, + b3: ::Type, + b4: Option<::Type>, + c: C, + c1: C::Type, + c2: Option, + c3: ::Type, + c4: Option<::Type>, + d: ::Type, + }, +} + +fn main() { + let e: Enum = + Enum::Seq(0, None, 0, PrivateStruct(0), 0, 0, None, 0, None, 0, 0, None, 0, None, 0); + assert_eq!(e, e); + + let e: Enum = Enum::Map { + m1: 0, + m2: None, + a1: 0, + a2: PrivateStruct(0), + b: 0, + b1: 0, + b2: None, + b3: 0, + b4: None, + c: 0, + c1: 0, + c2: None, + c3: 0, + c4: None, + d: 0, + }; + assert_eq!(e, e); + let e: TupleStruct = + TupleStruct(0, None, 0, PrivateStruct(0), 0, 0, None, 0, None, 0, 0, None, 0, None, 0); + assert_eq!(e, e); + + let e: Struct = Struct { + m1: 0, + m2: None, + a1: 0, + a2: PrivateStruct(0), + b: 0, + b1: 0, + b2: None, + b3: 0, + b4: None, + c: 0, + c1: 0, + c2: None, + c3: 0, + c4: None, + d: 0, + }; + assert_eq!(e, e); + + let e = Enum::Unit::; + assert_eq!(e, e); +} diff --git a/src/tools/miri/tests/pass/disable-alignment-check.rs b/src/tools/miri/tests/pass/disable-alignment-check.rs new file mode 100644 index 0000000000000..366aff4a9f8ec --- /dev/null +++ b/src/tools/miri/tests/pass/disable-alignment-check.rs @@ -0,0 +1,11 @@ +//@compile-flags: -Zmiri-disable-alignment-check + +fn main() { + let mut x = [0u8; 20]; + let x_ptr: *mut u8 = x.as_mut_ptr(); + // At least one of these is definitely unaligned. + unsafe { + *(x_ptr as *mut u64) = 42; + *(x_ptr.add(1) as *mut u64) = 42; + } +} diff --git a/src/tools/miri/tests/pass/drop_empty_slice.rs b/src/tools/miri/tests/pass/drop_empty_slice.rs new file mode 100644 index 0000000000000..9805ce0ace3f0 --- /dev/null +++ b/src/tools/miri/tests/pass/drop_empty_slice.rs @@ -0,0 +1,7 @@ +#![feature(box_syntax)] + +fn main() { + // With the nested Vec, this is calling Offset(Unique::empty(), 0) on drop. + let args: Vec> = Vec::new(); + let _val = box args; +} diff --git a/src/tools/miri/tests/pass/drop_on_array_elements.rs b/src/tools/miri/tests/pass/drop_on_array_elements.rs new file mode 100644 index 0000000000000..ae1ef036267e2 --- /dev/null +++ b/src/tools/miri/tests/pass/drop_on_array_elements.rs @@ -0,0 +1,24 @@ +struct Bar(u16); // ZSTs are tested separately + +static mut DROP_COUNT: usize = 0; + +impl Drop for Bar { + fn drop(&mut self) { + assert_eq!(self.0 as usize, unsafe { DROP_COUNT }); // tests whether we are called at a valid address + unsafe { + DROP_COUNT += 1; + } + } +} + +fn main() { + let b = [Bar(0), Bar(1), Bar(2), Bar(3)]; + assert_eq!(unsafe { DROP_COUNT }, 0); + drop(b); + assert_eq!(unsafe { DROP_COUNT }, 4); + + // check empty case + let b: [Bar; 0] = []; + drop(b); + assert_eq!(unsafe { DROP_COUNT }, 4); +} diff --git a/src/tools/miri/tests/pass/drop_on_fat_ptr_array_elements.rs b/src/tools/miri/tests/pass/drop_on_fat_ptr_array_elements.rs new file mode 100644 index 0000000000000..40025cd07f7e0 --- /dev/null +++ b/src/tools/miri/tests/pass/drop_on_fat_ptr_array_elements.rs @@ -0,0 +1,22 @@ +trait Foo {} + +struct Bar; + +impl Foo for Bar {} + +static mut DROP_COUNT: usize = 0; + +impl Drop for Bar { + fn drop(&mut self) { + unsafe { + DROP_COUNT += 1; + } + } +} + +fn main() { + let b: [Box; 4] = [Box::new(Bar), Box::new(Bar), Box::new(Bar), Box::new(Bar)]; + assert_eq!(unsafe { DROP_COUNT }, 0); + drop(b); + assert_eq!(unsafe { DROP_COUNT }, 4); +} diff --git a/src/tools/miri/tests/pass/drop_on_zst_array_elements.rs b/src/tools/miri/tests/pass/drop_on_zst_array_elements.rs new file mode 100644 index 0000000000000..babe098e4e6f5 --- /dev/null +++ b/src/tools/miri/tests/pass/drop_on_zst_array_elements.rs @@ -0,0 +1,23 @@ +struct Bar; + +static mut DROP_COUNT: usize = 0; + +impl Drop for Bar { + fn drop(&mut self) { + unsafe { + DROP_COUNT += 1; + } + } +} + +fn main() { + let b = [Bar, Bar, Bar, Bar]; + assert_eq!(unsafe { DROP_COUNT }, 0); + drop(b); + assert_eq!(unsafe { DROP_COUNT }, 4); + + // check empty case + let b: [Bar; 0] = []; + drop(b); + assert_eq!(unsafe { DROP_COUNT }, 4); +} diff --git a/src/tools/miri/tests/pass/drop_through_owned_slice.rs b/src/tools/miri/tests/pass/drop_through_owned_slice.rs new file mode 100644 index 0000000000000..8cdeb57d02f79 --- /dev/null +++ b/src/tools/miri/tests/pass/drop_through_owned_slice.rs @@ -0,0 +1,18 @@ +struct Bar; + +static mut DROP_COUNT: usize = 0; + +impl Drop for Bar { + fn drop(&mut self) { + unsafe { + DROP_COUNT += 1; + } + } +} + +fn main() { + let b: Box<[Bar]> = vec![Bar, Bar, Bar, Bar].into_boxed_slice(); + assert_eq!(unsafe { DROP_COUNT }, 0); + drop(b); + assert_eq!(unsafe { DROP_COUNT }, 4); +} diff --git a/src/tools/miri/tests/pass/drop_through_trait_object.rs b/src/tools/miri/tests/pass/drop_through_trait_object.rs new file mode 100644 index 0000000000000..8d22ca9ceb4ab --- /dev/null +++ b/src/tools/miri/tests/pass/drop_through_trait_object.rs @@ -0,0 +1,22 @@ +trait Foo {} + +struct Bar; + +static mut DROP_CALLED: bool = false; + +impl Drop for Bar { + fn drop(&mut self) { + unsafe { + DROP_CALLED = true; + } + } +} + +impl Foo for Bar {} + +fn main() { + let b: Box = Box::new(Bar); + assert!(unsafe { !DROP_CALLED }); + drop(b); + assert!(unsafe { DROP_CALLED }); +} diff --git a/src/tools/miri/tests/pass/drop_through_trait_object_rc.rs b/src/tools/miri/tests/pass/drop_through_trait_object_rc.rs new file mode 100644 index 0000000000000..7806c0252d270 --- /dev/null +++ b/src/tools/miri/tests/pass/drop_through_trait_object_rc.rs @@ -0,0 +1,24 @@ +trait Foo {} + +struct Bar; + +static mut DROP_CALLED: bool = false; + +impl Drop for Bar { + fn drop(&mut self) { + unsafe { + DROP_CALLED = true; + } + } +} + +impl Foo for Bar {} + +use std::rc::Rc; + +fn main() { + let b: Rc = Rc::new(Bar); + assert!(unsafe { !DROP_CALLED }); + drop(b); + assert!(unsafe { DROP_CALLED }); +} diff --git a/src/tools/miri/tests/pass/dst-field-align.rs b/src/tools/miri/tests/pass/dst-field-align.rs new file mode 100644 index 0000000000000..4d1db03643f79 --- /dev/null +++ b/src/tools/miri/tests/pass/dst-field-align.rs @@ -0,0 +1,66 @@ +#[allow(dead_code)] +struct Foo { + a: u16, + b: T, +} + +trait Bar { + fn get(&self) -> usize; +} + +impl Bar for usize { + fn get(&self) -> usize { + *self + } +} + +struct Baz { + a: T, +} + +#[allow(dead_code)] +struct HasDrop { + ptr: Box, + data: T, +} + +fn main() { + // Test that zero-offset works properly + let b: Baz = Baz { a: 7 }; + assert_eq!(b.a.get(), 7); + let b: &Baz = &b; + assert_eq!(b.a.get(), 7); + + // Test that the field is aligned properly + let f: Foo = Foo { a: 0, b: 11 }; + assert_eq!(f.b.get(), 11); + let ptr1: *const u8 = &f.b as *const _ as *const u8; + + let f: &Foo = &f; + let ptr2: *const u8 = &f.b as *const _ as *const u8; + assert_eq!(f.b.get(), 11); + + // The pointers should be the same + assert_eq!(ptr1, ptr2); + + // Test that nested DSTs work properly + let f: Foo> = Foo { a: 0, b: Foo { a: 1, b: 17 } }; + assert_eq!(f.b.b.get(), 17); + let f: &Foo> = &f; + assert_eq!(f.b.b.get(), 17); + + // Test that get the pointer via destructuring works + + let f: Foo = Foo { a: 0, b: 11 }; + let f: &Foo = &f; + let &Foo { a: _, b: ref bar } = f; + assert_eq!(bar.get(), 11); + + // Make sure that drop flags don't screw things up + + let d: HasDrop> = HasDrop { ptr: Box::new(0), data: Baz { a: [1, 2, 3, 4] } }; + assert_eq!([1, 2, 3, 4], d.data.a); + + let d: &HasDrop> = &d; + assert_eq!(&[1, 2, 3, 4], &d.data.a); +} diff --git a/src/tools/miri/tests/pass/dst-irrefutable-bind.rs b/src/tools/miri/tests/pass/dst-irrefutable-bind.rs new file mode 100644 index 0000000000000..fe7335c0c6564 --- /dev/null +++ b/src/tools/miri/tests/pass/dst-irrefutable-bind.rs @@ -0,0 +1,14 @@ +struct Test(T); + +fn main() { + let x = Test([1, 2, 3]); + let x: &Test<[i32]> = &x; + + let &ref _y = x; + + // Make sure binding to a fat pointer behind a reference + // still works + let slice = &[1, 2, 3]; + let x = Test(&slice); + let Test(&_slice) = x; +} diff --git a/src/tools/miri/tests/pass/dst-raw.rs b/src/tools/miri/tests/pass/dst-raw.rs new file mode 100644 index 0000000000000..f26191a1d5998 --- /dev/null +++ b/src/tools/miri/tests/pass/dst-raw.rs @@ -0,0 +1,94 @@ +// Test DST raw pointers + +trait Trait { + fn foo(&self) -> isize; +} + +struct A { + f: isize, +} +impl Trait for A { + fn foo(&self) -> isize { + self.f + } +} + +struct Foo { + f: T, +} + +pub fn main() { + // raw trait object + let x = A { f: 42 }; + let z: *const dyn Trait = &x; + let r = unsafe { (&*z).foo() }; + assert_eq!(r, 42); + + // raw DST struct + let p = Foo { f: A { f: 42 } }; + let o: *const Foo = &p; + let r = unsafe { (&*o).f.foo() }; + assert_eq!(r, 42); + + // raw slice + let a: *const [_] = &[1, 2, 3]; + unsafe { + let b = (*a)[2]; + assert_eq!(b, 3); + let len = (*a).len(); + assert_eq!(len, 3); + } + + // raw slice with explicit cast + let a = &[1, 2, 3] as *const [i32]; + unsafe { + let b = (*a)[2]; + assert_eq!(b, 3); + let len = (*a).len(); + assert_eq!(len, 3); + } + + // raw DST struct with slice + let c: *const Foo<[_]> = &Foo { f: [1, 2, 3] }; + unsafe { + let b = (&*c).f[0]; + assert_eq!(b, 1); + let len = (&*c).f.len(); + assert_eq!(len, 3); + } + + // all of the above with *mut + let mut x = A { f: 42 }; + let z: *mut dyn Trait = &mut x; + let r = unsafe { (&*z).foo() }; + assert_eq!(r, 42); + + let mut p = Foo { f: A { f: 42 } }; + let o: *mut Foo = &mut p; + let r = unsafe { (&*o).f.foo() }; + assert_eq!(r, 42); + + let a: *mut [_] = &mut [1, 2, 3]; + unsafe { + let b = (*a)[2]; + assert_eq!(b, 3); + let len = (*a).len(); + assert_eq!(len, 3); + } + + let a = &mut [1, 2, 3] as *mut [i32]; + unsafe { + let b = (*a)[2]; + assert_eq!(b, 3); + let len = (*a).len(); + assert_eq!(len, 3); + } + + let c: *mut Foo<[_]> = &mut Foo { f: [1, 2, 3] }; + unsafe { + let b = (&*c).f[0]; + assert_eq!(b, 1); + let len = (&*c).f.len(); + assert_eq!(len, 3); + } +} diff --git a/src/tools/miri/tests/pass/dst-struct-sole.rs b/src/tools/miri/tests/pass/dst-struct-sole.rs new file mode 100644 index 0000000000000..4b25fbb063006 --- /dev/null +++ b/src/tools/miri/tests/pass/dst-struct-sole.rs @@ -0,0 +1,74 @@ +// As dst-struct.rs, but the unsized field is the only field in the struct. + +struct Fat { + ptr: T, +} + +// x is a fat pointer +fn foo(x: &Fat<[isize]>) { + let y = &x.ptr; + assert_eq!(x.ptr.len(), 3); + assert_eq!(y[0], 1); + assert_eq!(x.ptr[1], 2); +} + +fn foo2(x: &Fat<[T]>) { + let y = &x.ptr; + let bar = Bar; + assert_eq!(x.ptr.len(), 3); + assert_eq!(y[0].to_bar(), bar); + assert_eq!(x.ptr[1].to_bar(), bar); +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +struct Bar; + +trait ToBar { + fn to_bar(&self) -> Bar; +} + +impl ToBar for Bar { + fn to_bar(&self) -> Bar { + *self + } +} + +pub fn main() { + // With a vec of ints. + let f1 = Fat { ptr: [1, 2, 3] }; + foo(&f1); + let f2 = &f1; + foo(f2); + let f3: &Fat<[isize]> = f2; + foo(f3); + let f4: &Fat<[isize]> = &f1; + foo(f4); + let f5: &Fat<[isize]> = &Fat { ptr: [1, 2, 3] }; + foo(f5); + + // With a vec of Bars. + let bar = Bar; + let f1 = Fat { ptr: [bar, bar, bar] }; + foo2(&f1); + let f2 = &f1; + foo2(f2); + let f3: &Fat<[Bar]> = f2; + foo2(f3); + let f4: &Fat<[Bar]> = &f1; + foo2(f4); + let f5: &Fat<[Bar]> = &Fat { ptr: [bar, bar, bar] }; + foo2(f5); + + // Assignment. + let f5: &mut Fat<[isize]> = &mut Fat { ptr: [1, 2, 3] }; + f5.ptr[1] = 34; + assert_eq!(f5.ptr[0], 1); + assert_eq!(f5.ptr[1], 34); + assert_eq!(f5.ptr[2], 3); + + // Zero size vec. + let f5: &Fat<[isize]> = &Fat { ptr: [] }; + assert!(f5.ptr.is_empty()); + let f5: &Fat<[Bar]> = &Fat { ptr: [] }; + assert!(f5.ptr.is_empty()); +} diff --git a/src/tools/miri/tests/pass/dst-struct.rs b/src/tools/miri/tests/pass/dst-struct.rs new file mode 100644 index 0000000000000..7191068eb2c40 --- /dev/null +++ b/src/tools/miri/tests/pass/dst-struct.rs @@ -0,0 +1,122 @@ +#![feature(box_syntax)] + +struct Fat { + f1: isize, + f2: &'static str, + ptr: T, +} + +// x is a fat pointer +fn foo(x: &Fat<[isize]>) { + let y = &x.ptr; + assert_eq!(x.ptr.len(), 3); + assert_eq!(y[0], 1); + assert_eq!(x.ptr[1], 2); + assert_eq!(x.f1, 5); + assert_eq!(x.f2, "some str"); +} + +fn foo2(x: &Fat<[T]>) { + let y = &x.ptr; + let bar = Bar; + assert_eq!(x.ptr.len(), 3); + assert_eq!(y[0].to_bar(), bar); + assert_eq!(x.ptr[1].to_bar(), bar); + assert_eq!(x.f1, 5); + assert_eq!(x.f2, "some str"); +} + +fn foo3(x: &Fat>) { + let y = &x.ptr.ptr; + assert_eq!(x.f1, 5); + assert_eq!(x.f2, "some str"); + assert_eq!(x.ptr.f1, 8); + assert_eq!(x.ptr.f2, "deep str"); + assert_eq!(x.ptr.ptr.len(), 3); + assert_eq!(y[0], 1); + assert_eq!(x.ptr.ptr[1], 2); +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +struct Bar; + +trait ToBar { + fn to_bar(&self) -> Bar; +} + +impl ToBar for Bar { + fn to_bar(&self) -> Bar { + *self + } +} + +pub fn main() { + // With a vec of ints. + let f1: Fat<[isize; 3]> = Fat { f1: 5, f2: "some str", ptr: [1, 2, 3] }; + foo(&f1); + let f2: &Fat<[isize; 3]> = &f1; + foo(f2); + let f3: &Fat<[isize]> = f2; + foo(f3); + let f4: &Fat<[isize]> = &f1; + foo(f4); + let f5: &Fat<[isize]> = &Fat { f1: 5, f2: "some str", ptr: [1, 2, 3] }; + foo(f5); + + // With a vec of Bars. + let bar = Bar; + let f1 = Fat { f1: 5, f2: "some str", ptr: [bar, bar, bar] }; + foo2(&f1); + let f2 = &f1; + foo2(f2); + let f3: &Fat<[Bar]> = f2; + foo2(f3); + let f4: &Fat<[Bar]> = &f1; + foo2(f4); + let f5: &Fat<[Bar]> = &Fat { f1: 5, f2: "some str", ptr: [bar, bar, bar] }; + foo2(f5); + + // Assignment. + let f5: &mut Fat<[isize]> = &mut Fat { f1: 5, f2: "some str", ptr: [1, 2, 3] }; + f5.ptr[1] = 34; + assert_eq!(f5.ptr[0], 1); + assert_eq!(f5.ptr[1], 34); + assert_eq!(f5.ptr[2], 3); + + // Zero size vec. + let f5: &Fat<[isize]> = &Fat { f1: 5, f2: "some str", ptr: [] }; + assert!(f5.ptr.is_empty()); + let f5: &Fat<[Bar]> = &Fat { f1: 5, f2: "some str", ptr: [] }; + assert!(f5.ptr.is_empty()); + + // Deeply nested. + let f1 = Fat { f1: 5, f2: "some str", ptr: Fat { f1: 8, f2: "deep str", ptr: [1, 2, 3] } }; + foo3(&f1); + let f2 = &f1; + foo3(f2); + let f3: &Fat> = f2; + foo3(f3); + let f4: &Fat> = &f1; + foo3(f4); + let f5: &Fat> = + &Fat { f1: 5, f2: "some str", ptr: Fat { f1: 8, f2: "deep str", ptr: [1, 2, 3] } }; + foo3(f5); + + // Box. + let f1 = Box::new([1, 2, 3]); + assert_eq!((*f1)[1], 2); + let f2: Box<[isize]> = f1; + assert_eq!((*f2)[1], 2); + + // Nested Box. + let f1: Box> = box Fat { f1: 5, f2: "some str", ptr: [1, 2, 3] }; + foo(&*f1); + let f2: Box> = f1; + foo(&*f2); + + let f3: Box> = + Box::>::new(Fat { f1: 5, f2: "some str", ptr: [1, 2, 3] }); + foo(&*f3); + let f4: Box> = box Fat { f1: 5, f2: "some str", ptr: [1, 2, 3] }; + foo(&*f4); +} diff --git a/src/tools/miri/tests/pass/dyn-arbitrary-self.rs b/src/tools/miri/tests/pass/dyn-arbitrary-self.rs new file mode 100644 index 0000000000000..256c72add9220 --- /dev/null +++ b/src/tools/miri/tests/pass/dyn-arbitrary-self.rs @@ -0,0 +1,128 @@ +#![feature(arbitrary_self_types, unsize, coerce_unsized, dispatch_from_dyn)] +#![feature(rustc_attrs)] + +fn pin_box_dyn() { + use std::pin::Pin; + + trait Foo { + fn bar(self: Pin<&mut Self>) -> bool; + } + + impl Foo for &'static str { + fn bar(self: Pin<&mut Self>) -> bool { + true + } + } + + let mut test: Pin> = Box::pin("foo"); + test.as_mut().bar(); +} + +fn stdlib_pointers() { + use std::{pin::Pin, rc::Rc, sync::Arc}; + + trait Trait { + fn by_rc(self: Rc) -> i64; + fn by_arc(self: Arc) -> i64; + fn by_pin_mut(self: Pin<&mut Self>) -> i64; + fn by_pin_box(self: Pin>) -> i64; + } + + impl Trait for i64 { + fn by_rc(self: Rc) -> i64 { + *self + } + fn by_arc(self: Arc) -> i64 { + *self + } + fn by_pin_mut(self: Pin<&mut Self>) -> i64 { + *self + } + fn by_pin_box(self: Pin>) -> i64 { + *self + } + } + + let rc = Rc::new(1i64) as Rc; + assert_eq!(1, rc.by_rc()); + + let arc = Arc::new(2i64) as Arc; + assert_eq!(2, arc.by_arc()); + + let mut value = 3i64; + let pin_mut = Pin::new(&mut value) as Pin<&mut dyn Trait>; + assert_eq!(3, pin_mut.by_pin_mut()); + + let pin_box = Into::>>::into(Box::new(4i64)) as Pin>; + assert_eq!(4, pin_box.by_pin_box()); +} + +fn pointers_and_wrappers() { + use std::{ + marker::Unsize, + ops::{CoerceUnsized, Deref, DispatchFromDyn}, + }; + + struct Ptr(Box); + + impl Deref for Ptr { + type Target = T; + + fn deref(&self) -> &T { + &*self.0 + } + } + + impl + ?Sized, U: ?Sized> CoerceUnsized> for Ptr {} + impl + ?Sized, U: ?Sized> DispatchFromDyn> for Ptr {} + + struct Wrapper(T); + + impl Deref for Wrapper { + type Target = T; + + fn deref(&self) -> &T { + &self.0 + } + } + + impl, U> CoerceUnsized> for Wrapper {} + impl, U> DispatchFromDyn> for Wrapper {} + + trait Trait { + // This method isn't object-safe yet. Unsized by-value `self` is object-safe (but not callable + // without unsized_locals), but wrappers arond `Self` currently are not. + // FIXME (mikeyhew) uncomment this when unsized rvalues object-safety is implemented + // fn wrapper(self: Wrapper) -> i32; + fn ptr_wrapper(self: Ptr>) -> i32; + fn wrapper_ptr(self: Wrapper>) -> i32; + fn wrapper_ptr_wrapper(self: Wrapper>>) -> i32; + } + + impl Trait for i32 { + fn ptr_wrapper(self: Ptr>) -> i32 { + **self + } + fn wrapper_ptr(self: Wrapper>) -> i32 { + **self + } + fn wrapper_ptr_wrapper(self: Wrapper>>) -> i32 { + ***self + } + } + + let pw = Ptr(Box::new(Wrapper(5))) as Ptr>; + assert_eq!(pw.ptr_wrapper(), 5); + + let wp = Wrapper(Ptr(Box::new(6))) as Wrapper>; + assert_eq!(wp.wrapper_ptr(), 6); + + let wpw = Wrapper(Ptr(Box::new(Wrapper(7)))) as Wrapper>>; + assert_eq!(wpw.wrapper_ptr_wrapper(), 7); +} + +fn main() { + pin_box_dyn(); + stdlib_pointers(); + pointers_and_wrappers(); +} diff --git a/src/tools/miri/tests/pass/dyn-traits.rs b/src/tools/miri/tests/pass/dyn-traits.rs new file mode 100644 index 0000000000000..908d521a0d816 --- /dev/null +++ b/src/tools/miri/tests/pass/dyn-traits.rs @@ -0,0 +1,147 @@ +fn ref_box_dyn() { + struct Struct(i32); + + trait Trait { + fn method(&self); + + fn box_method(self: Box); + } + + impl Trait for Struct { + fn method(&self) { + assert_eq!(self.0, 42); + } + + fn box_method(self: Box) { + assert_eq!(self.0, 7); + } + } + + struct Foo(T); + + let y: &dyn Trait = &Struct(42); + y.method(); + + let x: Foo = Foo(Struct(42)); + let y: &Foo = &x; + y.0.method(); + + let y: Box = Box::new(Struct(42)); + y.method(); + + let y = &y; + y.method(); + + let y: Box = Box::new(Struct(7)); + y.box_method(); +} + +fn box_box_trait() { + struct DroppableStruct; + + static mut DROPPED: bool = false; + + impl Drop for DroppableStruct { + fn drop(&mut self) { + unsafe { + DROPPED = true; + } + } + } + + trait MyTrait { + fn dummy(&self) {} + } + impl MyTrait for Box {} + + struct Whatever { + w: Box, + } + + impl Whatever { + fn new(w: Box) -> Whatever { + Whatever { w: w } + } + } + + { + let f = Box::new(DroppableStruct); + let a = Whatever::new(Box::new(f) as Box); + a.w.dummy(); + } + assert!(unsafe { DROPPED }); +} + +// Disabled for now: unsized locals are not supported, +// their current MIR encoding is just not great. +/* +fn unsized_dyn() { + pub trait Foo { + fn foo(self) -> String; + } + + struct A; + + impl Foo for A { + fn foo(self) -> String { + format!("hello") + } + } + + let x = *(Box::new(A) as Box); + assert_eq!(x.foo(), format!("hello")); + + // I'm not sure whether we want this to work + let x = Box::new(A) as Box; + assert_eq!(x.foo(), format!("hello")); +} +fn unsized_dyn_autoderef() { + pub trait Foo { + fn foo(self) -> String; + } + + impl Foo for [char] { + fn foo(self) -> String { + self.iter().collect() + } + } + + impl Foo for str { + fn foo(self) -> String { + self.to_owned() + } + } + + impl Foo for dyn FnMut() -> String { + fn foo(mut self) -> String { + self() + } + } + + let x = *(Box::new(['h', 'e', 'l', 'l', 'o']) as Box<[char]>); + assert_eq!(&x.foo() as &str, "hello"); + + let x = Box::new(['h', 'e', 'l', 'l', 'o']) as Box<[char]>; + assert_eq!(&x.foo() as &str, "hello"); + + let x = "hello".to_owned().into_boxed_str(); + assert_eq!(&x.foo() as &str, "hello"); + + let x = *("hello".to_owned().into_boxed_str()); + assert_eq!(&x.foo() as &str, "hello"); + + let x = "hello".to_owned().into_boxed_str(); + assert_eq!(&x.foo() as &str, "hello"); + + let x = *(Box::new(|| "hello".to_owned()) as Box String>); + assert_eq!(&x.foo() as &str, "hello"); + + let x = Box::new(|| "hello".to_owned()) as Box String>; + assert_eq!(&x.foo() as &str, "hello"); +} +*/ + +fn main() { + ref_box_dyn(); + box_box_trait(); +} diff --git a/src/tools/miri/tests/pass/dyn-upcast.rs b/src/tools/miri/tests/pass/dyn-upcast.rs new file mode 100644 index 0000000000000..8432012a9ba52 --- /dev/null +++ b/src/tools/miri/tests/pass/dyn-upcast.rs @@ -0,0 +1,427 @@ +#![feature(trait_upcasting)] +#![allow(incomplete_features)] + +fn main() { + basic(); + diamond(); + struct_(); + replace_vptr(); + vtable_mismatch_nop_cast(); +} + +fn vtable_mismatch_nop_cast() { + let ptr: &dyn std::fmt::Display = &0; + // Even though the vtable is for the wrong trait, this cast doesn't actually change the needed + // vtable so it should still be allowed. + let ptr: *const (dyn std::fmt::Debug + Send + Sync) = unsafe { std::mem::transmute(ptr) }; + let _ptr2 = ptr as *const dyn std::fmt::Debug; +} + +fn basic() { + trait Foo: PartialEq + std::fmt::Debug + Send + Sync { + fn a(&self) -> i32 { + 10 + } + + fn z(&self) -> i32 { + 11 + } + + fn y(&self) -> i32 { + 12 + } + } + + trait Bar: Foo { + fn b(&self) -> i32 { + 20 + } + + fn w(&self) -> i32 { + 21 + } + } + + trait Baz: Bar { + fn c(&self) -> i32 { + 30 + } + } + + impl Foo for i32 { + fn a(&self) -> i32 { + 100 + } + } + + impl Bar for i32 { + fn b(&self) -> i32 { + 200 + } + } + + impl Baz for i32 { + fn c(&self) -> i32 { + 300 + } + } + + let baz: &dyn Baz = &1; + let _: &dyn std::fmt::Debug = baz; + assert_eq!(*baz, 1); + assert_eq!(baz.a(), 100); + assert_eq!(baz.b(), 200); + assert_eq!(baz.c(), 300); + assert_eq!(baz.z(), 11); + assert_eq!(baz.y(), 12); + assert_eq!(baz.w(), 21); + + let bar: &dyn Bar = baz; + let _: &dyn std::fmt::Debug = bar; + assert_eq!(*bar, 1); + assert_eq!(bar.a(), 100); + assert_eq!(bar.b(), 200); + assert_eq!(bar.z(), 11); + assert_eq!(bar.y(), 12); + assert_eq!(bar.w(), 21); + + let foo: &dyn Foo = baz; + let _: &dyn std::fmt::Debug = foo; + assert_eq!(*foo, 1); + assert_eq!(foo.a(), 100); + assert_eq!(foo.z(), 11); + assert_eq!(foo.y(), 12); + + let foo: &dyn Foo = bar; + let _: &dyn std::fmt::Debug = foo; + assert_eq!(*foo, 1); + assert_eq!(foo.a(), 100); + assert_eq!(foo.z(), 11); + assert_eq!(foo.y(), 12); +} + +fn diamond() { + trait Foo: PartialEq + std::fmt::Debug + Send + Sync { + fn a(&self) -> i32 { + 10 + } + + fn z(&self) -> i32 { + 11 + } + + fn y(&self) -> i32 { + 12 + } + } + + trait Bar1: Foo { + fn b(&self) -> i32 { + 20 + } + + fn w(&self) -> i32 { + 21 + } + } + + trait Bar2: Foo { + fn c(&self) -> i32 { + 30 + } + + fn v(&self) -> i32 { + 31 + } + } + + trait Baz: Bar1 + Bar2 { + fn d(&self) -> i32 { + 40 + } + } + + impl Foo for i32 { + fn a(&self) -> i32 { + 100 + } + } + + impl Bar1 for i32 { + fn b(&self) -> i32 { + 200 + } + } + + impl Bar2 for i32 { + fn c(&self) -> i32 { + 300 + } + } + + impl Baz for i32 { + fn d(&self) -> i32 { + 400 + } + } + + let baz: &dyn Baz = &1; + let _: &dyn std::fmt::Debug = baz; + assert_eq!(*baz, 1); + assert_eq!(baz.a(), 100); + assert_eq!(baz.b(), 200); + assert_eq!(baz.c(), 300); + assert_eq!(baz.d(), 400); + assert_eq!(baz.z(), 11); + assert_eq!(baz.y(), 12); + assert_eq!(baz.w(), 21); + assert_eq!(baz.v(), 31); + + let bar1: &dyn Bar1 = baz; + let _: &dyn std::fmt::Debug = bar1; + assert_eq!(*bar1, 1); + assert_eq!(bar1.a(), 100); + assert_eq!(bar1.b(), 200); + assert_eq!(bar1.z(), 11); + assert_eq!(bar1.y(), 12); + assert_eq!(bar1.w(), 21); + + let bar2: &dyn Bar2 = baz; + let _: &dyn std::fmt::Debug = bar2; + assert_eq!(*bar2, 1); + assert_eq!(bar2.a(), 100); + assert_eq!(bar2.c(), 300); + assert_eq!(bar2.z(), 11); + assert_eq!(bar2.y(), 12); + assert_eq!(bar2.v(), 31); + + let foo: &dyn Foo = baz; + let _: &dyn std::fmt::Debug = foo; + assert_eq!(*foo, 1); + assert_eq!(foo.a(), 100); + + let foo: &dyn Foo = bar1; + let _: &dyn std::fmt::Debug = foo; + assert_eq!(*foo, 1); + assert_eq!(foo.a(), 100); + + let foo: &dyn Foo = bar2; + let _: &dyn std::fmt::Debug = foo; + assert_eq!(*foo, 1); + assert_eq!(foo.a(), 100); +} + +fn struct_() { + use std::rc::Rc; + use std::sync::Arc; + + trait Foo: PartialEq + std::fmt::Debug + Send + Sync { + fn a(&self) -> i32 { + 10 + } + + fn z(&self) -> i32 { + 11 + } + + fn y(&self) -> i32 { + 12 + } + } + + trait Bar: Foo { + fn b(&self) -> i32 { + 20 + } + + fn w(&self) -> i32 { + 21 + } + } + + trait Baz: Bar { + fn c(&self) -> i32 { + 30 + } + } + + impl Foo for i32 { + fn a(&self) -> i32 { + 100 + } + } + + impl Bar for i32 { + fn b(&self) -> i32 { + 200 + } + } + + impl Baz for i32 { + fn c(&self) -> i32 { + 300 + } + } + + fn test_box() { + let v = Box::new(1); + + let baz: Box = v.clone(); + assert_eq!(*baz, 1); + assert_eq!(baz.a(), 100); + assert_eq!(baz.b(), 200); + assert_eq!(baz.c(), 300); + assert_eq!(baz.z(), 11); + assert_eq!(baz.y(), 12); + assert_eq!(baz.w(), 21); + + let baz: Box = v.clone(); + let bar: Box = baz; + assert_eq!(*bar, 1); + assert_eq!(bar.a(), 100); + assert_eq!(bar.b(), 200); + assert_eq!(bar.z(), 11); + assert_eq!(bar.y(), 12); + assert_eq!(bar.w(), 21); + + let baz: Box = v.clone(); + let foo: Box = baz; + assert_eq!(*foo, 1); + assert_eq!(foo.a(), 100); + assert_eq!(foo.z(), 11); + assert_eq!(foo.y(), 12); + + let baz: Box = v.clone(); + let bar: Box = baz; + let foo: Box = bar; + assert_eq!(*foo, 1); + assert_eq!(foo.a(), 100); + assert_eq!(foo.z(), 11); + assert_eq!(foo.y(), 12); + } + + fn test_rc() { + let v = Rc::new(1); + + let baz: Rc = v.clone(); + assert_eq!(*baz, 1); + assert_eq!(baz.a(), 100); + assert_eq!(baz.b(), 200); + assert_eq!(baz.c(), 300); + assert_eq!(baz.z(), 11); + assert_eq!(baz.y(), 12); + assert_eq!(baz.w(), 21); + + let baz: Rc = v.clone(); + let bar: Rc = baz; + assert_eq!(*bar, 1); + assert_eq!(bar.a(), 100); + assert_eq!(bar.b(), 200); + assert_eq!(bar.z(), 11); + assert_eq!(bar.y(), 12); + assert_eq!(bar.w(), 21); + + let baz: Rc = v.clone(); + let foo: Rc = baz; + assert_eq!(*foo, 1); + assert_eq!(foo.a(), 100); + assert_eq!(foo.z(), 11); + assert_eq!(foo.y(), 12); + + let baz: Rc = v.clone(); + let bar: Rc = baz; + let foo: Rc = bar; + assert_eq!(*foo, 1); + assert_eq!(foo.a(), 100); + assert_eq!(foo.z(), 11); + assert_eq!(foo.y(), 12); + assert_eq!(foo.z(), 11); + assert_eq!(foo.y(), 12); + } + + fn test_arc() { + let v = Arc::new(1); + + let baz: Arc = v.clone(); + assert_eq!(*baz, 1); + assert_eq!(baz.a(), 100); + assert_eq!(baz.b(), 200); + assert_eq!(baz.c(), 300); + assert_eq!(baz.z(), 11); + assert_eq!(baz.y(), 12); + assert_eq!(baz.w(), 21); + + let baz: Arc = v.clone(); + let bar: Arc = baz; + assert_eq!(*bar, 1); + assert_eq!(bar.a(), 100); + assert_eq!(bar.b(), 200); + assert_eq!(bar.z(), 11); + assert_eq!(bar.y(), 12); + assert_eq!(bar.w(), 21); + + let baz: Arc = v.clone(); + let foo: Arc = baz; + assert_eq!(*foo, 1); + assert_eq!(foo.a(), 100); + assert_eq!(foo.z(), 11); + assert_eq!(foo.y(), 12); + + let baz: Arc = v.clone(); + let bar: Arc = baz; + let foo: Arc = bar; + assert_eq!(*foo, 1); + assert_eq!(foo.a(), 100); + assert_eq!(foo.z(), 11); + assert_eq!(foo.y(), 12); + } + + test_box(); + test_rc(); + test_arc(); +} + +fn replace_vptr() { + trait A { + fn foo_a(&self); + } + + trait B { + fn foo_b(&self); + } + + trait C: A + B { + fn foo_c(&self); + } + + struct S(i32); + + impl A for S { + fn foo_a(&self) { + unreachable!(); + } + } + + impl B for S { + fn foo_b(&self) { + assert_eq!(42, self.0); + } + } + + impl C for S { + fn foo_c(&self) { + unreachable!(); + } + } + + fn invoke_inner(b: &dyn B) { + b.foo_b(); + } + + fn invoke_outer(c: &dyn C) { + invoke_inner(c); + } + + let s = S(42); + invoke_outer(&s); +} diff --git a/src/tools/miri/tests/pass/enum-nullable-const-null-with-fields.rs b/src/tools/miri/tests/pass/enum-nullable-const-null-with-fields.rs new file mode 100644 index 0000000000000..8385cc5d880c6 --- /dev/null +++ b/src/tools/miri/tests/pass/enum-nullable-const-null-with-fields.rs @@ -0,0 +1,11 @@ +use std::result::Result; +use std::result::Result::Ok; + +static C: Result<(), Box> = Ok(()); + +// This is because of yet another bad assertion (ICE) about the null side of a nullable enum. +// So we won't actually compile if the bug is present, but we check the value in main anyway. + +pub fn main() { + assert!(C.is_ok()); +} diff --git a/src/tools/miri/tests/pass/enum_discriminant_ptr_value.rs b/src/tools/miri/tests/pass/enum_discriminant_ptr_value.rs new file mode 100644 index 0000000000000..4a3820777cff7 --- /dev/null +++ b/src/tools/miri/tests/pass/enum_discriminant_ptr_value.rs @@ -0,0 +1,9 @@ +// A niche-optimized enum where the discriminant is a pointer value -- relies on ptr-to-int casts in +// the niche handling code. +//@compile-flags: -Zmiri-disable-stacked-borrows -Zmiri-disable-validation + +fn main() { + let x = 42; + let val: Option<&i32> = unsafe { std::mem::transmute((&x as *const i32).wrapping_offset(2)) }; + assert!(val.is_some()); +} diff --git a/src/tools/miri/tests/pass/enums.rs b/src/tools/miri/tests/pass/enums.rs new file mode 100644 index 0000000000000..ac7aafc1bb2e3 --- /dev/null +++ b/src/tools/miri/tests/pass/enums.rs @@ -0,0 +1,143 @@ +enum MyEnum { + MyEmptyVariant, + MyNewtypeVariant(i32), + MyTupleVariant(i32, i32), + MyStructVariant { my_first_field: i32, my_second_field: i32 }, +} + +fn test(me: MyEnum) { + match me { + MyEnum::MyEmptyVariant => {} + MyEnum::MyNewtypeVariant(ref val) => assert_eq!(val, &42), + MyEnum::MyTupleVariant(ref a, ref b) => { + assert_eq!(a, &43); + assert_eq!(b, &44); + } + MyEnum::MyStructVariant { ref my_first_field, ref my_second_field } => { + assert_eq!(my_first_field, &45); + assert_eq!(my_second_field, &46); + } + } +} + +fn discriminant_overflow() { + // Tests for https://github.com/rust-lang/rust/issues/62138. + #[repr(u8)] + #[allow(dead_code)] + enum WithWraparoundInvalidValues { + X = 1, + Y = 254, + } + + #[allow(dead_code)] + enum Foo { + A, + B, + C(WithWraparoundInvalidValues), + } + + let x = Foo::B; + match x { + Foo::B => {} + _ => panic!(), + } +} + +fn more_discriminant_overflow() { + pub enum Infallible {} + + // The check that the `bool` field of `V1` is encoding a "niche variant" + // (i.e. not `V1`, so `V3` or `V4`) used to be mathematically incorrect, + // causing valid `V1` values to be interpreted as other variants. + #[allow(dead_code)] + pub enum E1 { + V1 { f: bool }, + V2 { f: Infallible }, + V3, + V4, + } + + // Computing the discriminant used to be done using the niche type (here `u8`, + // from the `bool` field of `V1`), overflowing for variants with large enough + // indices (`V3` and `V4`), causing them to be interpreted as other variants. + #[allow(dead_code)] + #[rustfmt::skip] // rustfmt prefers every variant on its own line + pub enum E2 { + V1 { f: bool }, + + /*_00*/ _01(X), _02(X), _03(X), _04(X), _05(X), _06(X), _07(X), + _08(X), _09(X), _0A(X), _0B(X), _0C(X), _0D(X), _0E(X), _0F(X), + _10(X), _11(X), _12(X), _13(X), _14(X), _15(X), _16(X), _17(X), + _18(X), _19(X), _1A(X), _1B(X), _1C(X), _1D(X), _1E(X), _1F(X), + _20(X), _21(X), _22(X), _23(X), _24(X), _25(X), _26(X), _27(X), + _28(X), _29(X), _2A(X), _2B(X), _2C(X), _2D(X), _2E(X), _2F(X), + _30(X), _31(X), _32(X), _33(X), _34(X), _35(X), _36(X), _37(X), + _38(X), _39(X), _3A(X), _3B(X), _3C(X), _3D(X), _3E(X), _3F(X), + _40(X), _41(X), _42(X), _43(X), _44(X), _45(X), _46(X), _47(X), + _48(X), _49(X), _4A(X), _4B(X), _4C(X), _4D(X), _4E(X), _4F(X), + _50(X), _51(X), _52(X), _53(X), _54(X), _55(X), _56(X), _57(X), + _58(X), _59(X), _5A(X), _5B(X), _5C(X), _5D(X), _5E(X), _5F(X), + _60(X), _61(X), _62(X), _63(X), _64(X), _65(X), _66(X), _67(X), + _68(X), _69(X), _6A(X), _6B(X), _6C(X), _6D(X), _6E(X), _6F(X), + _70(X), _71(X), _72(X), _73(X), _74(X), _75(X), _76(X), _77(X), + _78(X), _79(X), _7A(X), _7B(X), _7C(X), _7D(X), _7E(X), _7F(X), + _80(X), _81(X), _82(X), _83(X), _84(X), _85(X), _86(X), _87(X), + _88(X), _89(X), _8A(X), _8B(X), _8C(X), _8D(X), _8E(X), _8F(X), + _90(X), _91(X), _92(X), _93(X), _94(X), _95(X), _96(X), _97(X), + _98(X), _99(X), _9A(X), _9B(X), _9C(X), _9D(X), _9E(X), _9F(X), + _A0(X), _A1(X), _A2(X), _A3(X), _A4(X), _A5(X), _A6(X), _A7(X), + _A8(X), _A9(X), _AA(X), _AB(X), _AC(X), _AD(X), _AE(X), _AF(X), + _B0(X), _B1(X), _B2(X), _B3(X), _B4(X), _B5(X), _B6(X), _B7(X), + _B8(X), _B9(X), _BA(X), _BB(X), _BC(X), _BD(X), _BE(X), _BF(X), + _C0(X), _C1(X), _C2(X), _C3(X), _C4(X), _C5(X), _C6(X), _C7(X), + _C8(X), _C9(X), _CA(X), _CB(X), _CC(X), _CD(X), _CE(X), _CF(X), + _D0(X), _D1(X), _D2(X), _D3(X), _D4(X), _D5(X), _D6(X), _D7(X), + _D8(X), _D9(X), _DA(X), _DB(X), _DC(X), _DD(X), _DE(X), _DF(X), + _E0(X), _E1(X), _E2(X), _E3(X), _E4(X), _E5(X), _E6(X), _E7(X), + _E8(X), _E9(X), _EA(X), _EB(X), _EC(X), _ED(X), _EE(X), _EF(X), + _F0(X), _F1(X), _F2(X), _F3(X), _F4(X), _F5(X), _F6(X), _F7(X), + _F8(X), _F9(X), _FA(X), _FB(X), _FC(X), _FD(X), _FE(X), _FF(X), + + V3, + V4, + } + + if let E1::V2 { .. } = (E1::V1 { f: true }) { + unreachable!() + } + if let E1::V1 { .. } = (E1::V1 { f: true }) { + } else { + unreachable!() + } + + if let E2::V1 { .. } = E2::V3:: { + unreachable!() + } + if let E2::V3 { .. } = E2::V3:: { + } else { + unreachable!() + } +} + +fn overaligned_casts() { + #[allow(dead_code)] + #[repr(align(8))] + enum Aligned { + Zero = 0, + One = 1, + } + + let aligned = Aligned::Zero; + assert_eq!(aligned as u8, 0); +} + +fn main() { + test(MyEnum::MyEmptyVariant); + test(MyEnum::MyNewtypeVariant(42)); + test(MyEnum::MyTupleVariant(43, 44)); + test(MyEnum::MyStructVariant { my_first_field: 45, my_second_field: 46 }); + + discriminant_overflow(); + more_discriminant_overflow(); + overaligned_casts(); +} diff --git a/src/tools/miri/tests/pass/extern_crate_std_in_main.rs b/src/tools/miri/tests/pass/extern_crate_std_in_main.rs new file mode 100644 index 0000000000000..8f71e613375e3 --- /dev/null +++ b/src/tools/miri/tests/pass/extern_crate_std_in_main.rs @@ -0,0 +1,5 @@ +#![no_std] + +fn main() { + extern crate std; +} diff --git a/src/tools/miri/tests/pass/extern_types.rs b/src/tools/miri/tests/pass/extern_types.rs new file mode 100644 index 0000000000000..aa4c65ea8928a --- /dev/null +++ b/src/tools/miri/tests/pass/extern_types.rs @@ -0,0 +1,10 @@ +#![feature(extern_types)] + +extern "C" { + type Foo; +} + +fn main() { + let x: &Foo = unsafe { &*(16 as *const Foo) }; + let _y: &Foo = &*x; +} diff --git a/src/tools/miri/tests/pass/extern_types.stderr b/src/tools/miri/tests/pass/extern_types.stderr new file mode 100644 index 0000000000000..2e18f69305896 --- /dev/null +++ b/src/tools/miri/tests/pass/extern_types.stderr @@ -0,0 +1,15 @@ +warning: integer-to-pointer cast + --> $DIR/extern_types.rs:LL:CC + | +LL | let x: &Foo = unsafe { &*(16 as *const Foo) }; + | ^^^^^^^^^^^^^^^^^^ integer-to-pointer cast + | + = help: This program is using integer-to-pointer casts or (equivalently) `ptr::from_exposed_addr`, + = help: which means that Miri might miss pointer bugs in this program. + = help: See https://doc.rust-lang.org/nightly/std/ptr/fn.from_exposed_addr.html for more details on that operation. + = help: To ensure that Miri does not miss bugs in your program, use Strict Provenance APIs (https://doc.rust-lang.org/nightly/std/ptr/index.html#strict-provenance, https://crates.io/crates/sptr) instead. + = help: You can then pass the `-Zmiri-strict-provenance` flag to Miri, to ensure you are not relying on `from_exposed_addr` semantics. + = help: Alternatively, the `-Zmiri-permissive-provenance` flag disables this warning. + = note: BACKTRACE: + = note: inside `main` at $DIR/extern_types.rs:LL:CC + diff --git a/src/tools/miri/tests/pass/fat_ptr.rs b/src/tools/miri/tests/pass/fat_ptr.rs new file mode 100644 index 0000000000000..8317156a218dd --- /dev/null +++ b/src/tools/miri/tests/pass/fat_ptr.rs @@ -0,0 +1,51 @@ +// test that ordinary fat pointer operations work. + +struct Wrapper(u32, T); + +struct FatPtrContainer<'a> { + ptr: &'a [u8], +} + +fn fat_ptr_project(a: &Wrapper<[u8]>) -> &[u8] { + &a.1 +} + +fn fat_ptr_simple(a: &[u8]) -> &[u8] { + a +} + +fn fat_ptr_via_local(a: &[u8]) -> &[u8] { + let x = a; + x +} + +fn fat_ptr_from_struct(s: FatPtrContainer) -> &[u8] { + s.ptr +} + +fn fat_ptr_to_struct(a: &[u8]) -> FatPtrContainer { + FatPtrContainer { ptr: a } +} + +fn fat_ptr_store_to<'a>(a: &'a [u8], b: &mut &'a [u8]) { + *b = a; +} + +fn fat_ptr_constant() -> &'static str { + "HELLO" +} + +fn main() { + let a = Wrapper(4, [7, 6, 5]); + + let p = fat_ptr_project(&a); + let p = fat_ptr_simple(p); + let p = fat_ptr_via_local(p); + let p = fat_ptr_from_struct(fat_ptr_to_struct(p)); + + let mut target: &[u8] = &[42]; + fat_ptr_store_to(p, &mut target); + assert_eq!(target, &a.1); + + assert_eq!(fat_ptr_constant(), "HELLO"); +} diff --git a/src/tools/miri/tests/pass/float.rs b/src/tools/miri/tests/pass/float.rs new file mode 100644 index 0000000000000..48dd99441ebff --- /dev/null +++ b/src/tools/miri/tests/pass/float.rs @@ -0,0 +1,555 @@ +#![feature(stmt_expr_attributes, bench_black_box)] +#![allow(arithmetic_overflow)] +use std::fmt::Debug; +use std::hint::black_box; + +fn main() { + basic(); + casts(); + more_casts(); + ops(); + nan_casts(); +} + +// Helper function to avoid promotion so that this tests "run-time" casts, not CTFE. +// Doesn't make a big difference when running this in Miri, but it means we can compare this +// with the LLVM backend by running `rustc -Zmir-opt-level=0 -Zsaturating-float-casts`. +#[track_caller] +#[inline(never)] +fn assert_eq(x: T, y: T) { + assert_eq!(x, y); +} + +trait FloatToInt: Copy { + fn cast(self) -> Int; + unsafe fn cast_unchecked(self) -> Int; +} + +impl FloatToInt for f32 { + fn cast(self) -> i8 { + self as _ + } + unsafe fn cast_unchecked(self) -> i8 { + self.to_int_unchecked() + } +} +impl FloatToInt for f32 { + fn cast(self) -> i32 { + self as _ + } + unsafe fn cast_unchecked(self) -> i32 { + self.to_int_unchecked() + } +} +impl FloatToInt for f32 { + fn cast(self) -> u32 { + self as _ + } + unsafe fn cast_unchecked(self) -> u32 { + self.to_int_unchecked() + } +} +impl FloatToInt for f32 { + fn cast(self) -> i64 { + self as _ + } + unsafe fn cast_unchecked(self) -> i64 { + self.to_int_unchecked() + } +} +impl FloatToInt for f32 { + fn cast(self) -> u64 { + self as _ + } + unsafe fn cast_unchecked(self) -> u64 { + self.to_int_unchecked() + } +} + +impl FloatToInt for f64 { + fn cast(self) -> i8 { + self as _ + } + unsafe fn cast_unchecked(self) -> i8 { + self.to_int_unchecked() + } +} +impl FloatToInt for f64 { + fn cast(self) -> i32 { + self as _ + } + unsafe fn cast_unchecked(self) -> i32 { + self.to_int_unchecked() + } +} +impl FloatToInt for f64 { + fn cast(self) -> u32 { + self as _ + } + unsafe fn cast_unchecked(self) -> u32 { + self.to_int_unchecked() + } +} +impl FloatToInt for f64 { + fn cast(self) -> i64 { + self as _ + } + unsafe fn cast_unchecked(self) -> i64 { + self.to_int_unchecked() + } +} +impl FloatToInt for f64 { + fn cast(self) -> u64 { + self as _ + } + unsafe fn cast_unchecked(self) -> u64 { + self.to_int_unchecked() + } +} +impl FloatToInt for f64 { + fn cast(self) -> i128 { + self as _ + } + unsafe fn cast_unchecked(self) -> i128 { + self.to_int_unchecked() + } +} +impl FloatToInt for f64 { + fn cast(self) -> u128 { + self as _ + } + unsafe fn cast_unchecked(self) -> u128 { + self.to_int_unchecked() + } +} + +/// Test this cast both via `as` and via `approx_unchecked` (i.e., it must not saturate). +#[track_caller] +#[inline(never)] +fn test_both_cast(x: F, y: I) +where + F: FloatToInt, + I: PartialEq + Debug, +{ + assert_eq!(x.cast(), y); + assert_eq!(unsafe { x.cast_unchecked() }, y); +} + +fn basic() { + // basic arithmetic + assert_eq(6.0_f32 * 6.0_f32, 36.0_f32); + assert_eq(6.0_f64 * 6.0_f64, 36.0_f64); + assert_eq(-{ 5.0_f32 }, -5.0_f32); + assert_eq(-{ 5.0_f64 }, -5.0_f64); + // infinities, NaN + assert!((5.0_f32 / 0.0).is_infinite()); + assert_ne!({ 5.0_f32 / 0.0 }, { -5.0_f32 / 0.0 }); + assert!((5.0_f64 / 0.0).is_infinite()); + assert_ne!({ 5.0_f64 / 0.0 }, { 5.0_f64 / -0.0 }); + assert!((-5.0_f32).sqrt().is_nan()); + assert!((-5.0_f64).sqrt().is_nan()); + assert_ne!(f32::NAN, f32::NAN); + assert_ne!(f64::NAN, f64::NAN); + // negative zero + let posz = 0.0f32; + let negz = -0.0f32; + assert_eq(posz, negz); + assert_ne!(posz.to_bits(), negz.to_bits()); + let posz = 0.0f64; + let negz = -0.0f64; + assert_eq(posz, negz); + assert_ne!(posz.to_bits(), negz.to_bits()); + // byte-level transmute + let x: u64 = unsafe { std::mem::transmute(42.0_f64) }; + let y: f64 = unsafe { std::mem::transmute(x) }; + assert_eq(y, 42.0_f64); + let x: u32 = unsafe { std::mem::transmute(42.0_f32) }; + let y: f32 = unsafe { std::mem::transmute(x) }; + assert_eq(y, 42.0_f32); +} + +/// Many of these test values are taken from +/// https://github.com/WebAssembly/testsuite/blob/master/conversions.wast. +fn casts() { + // f32 -> i8 + test_both_cast::(127.99, 127); + test_both_cast::(-128.99, -128); + + // f32 -> i32 + test_both_cast::(0.0, 0); + test_both_cast::(-0.0, 0); + test_both_cast::(/*0x1p-149*/ f32::from_bits(0x00000001), 0); + test_both_cast::(/*-0x1p-149*/ f32::from_bits(0x80000001), 0); + test_both_cast::(/*0x1.19999ap+0*/ f32::from_bits(0x3f8ccccd), 1); + test_both_cast::(/*-0x1.19999ap+0*/ f32::from_bits(0xbf8ccccd), -1); + test_both_cast::(1.9, 1); + test_both_cast::(-1.9, -1); + test_both_cast::(5.0, 5); + test_both_cast::(-5.0, -5); + test_both_cast::(2147483520.0, 2147483520); + test_both_cast::(-2147483648.0, -2147483648); + // unrepresentable casts + assert_eq::(2147483648.0f32 as i32, i32::MAX); + assert_eq::(-2147483904.0f32 as i32, i32::MIN); + assert_eq::(f32::MAX as i32, i32::MAX); + assert_eq::(f32::MIN as i32, i32::MIN); + assert_eq::(f32::INFINITY as i32, i32::MAX); + assert_eq::(f32::NEG_INFINITY as i32, i32::MIN); + assert_eq::(f32::NAN as i32, 0); + assert_eq::((-f32::NAN) as i32, 0); + + // f32 -> u32 + test_both_cast::(0.0, 0); + test_both_cast::(-0.0, 0); + test_both_cast::(-0.9999999, 0); + test_both_cast::(/*0x1p-149*/ f32::from_bits(0x1), 0); + test_both_cast::(/*-0x1p-149*/ f32::from_bits(0x80000001), 0); + test_both_cast::(/*0x1.19999ap+0*/ f32::from_bits(0x3f8ccccd), 1); + test_both_cast::(1.9, 1); + test_both_cast::(5.0, 5); + test_both_cast::(2147483648.0, 0x8000_0000); + test_both_cast::(4294967040.0, 0u32.wrapping_sub(256)); + test_both_cast::(/*-0x1.ccccccp-1*/ f32::from_bits(0xbf666666), 0); + test_both_cast::(/*-0x1.fffffep-1*/ f32::from_bits(0xbf7fffff), 0); + test_both_cast::((u32::MAX - 128) as f32, u32::MAX - 255); // rounding loss + // unrepresentable casts + assert_eq::((u32::MAX - 127) as f32 as u32, u32::MAX); // rounds up and then becomes unrepresentable + assert_eq::(4294967296.0f32 as u32, u32::MAX); + assert_eq::(-5.0f32 as u32, 0); + assert_eq::(f32::MAX as u32, u32::MAX); + assert_eq::(f32::MIN as u32, 0); + assert_eq::(f32::INFINITY as u32, u32::MAX); + assert_eq::(f32::NEG_INFINITY as u32, 0); + assert_eq::(f32::NAN as u32, 0); + assert_eq::((-f32::NAN) as u32, 0); + + // f32 -> i64 + test_both_cast::(4294967296.0, 4294967296); + test_both_cast::(-4294967296.0, -4294967296); + test_both_cast::(9223371487098961920.0, 9223371487098961920); + test_both_cast::(-9223372036854775808.0, -9223372036854775808); + + // f64 -> i8 + test_both_cast::(127.99, 127); + test_both_cast::(-128.99, -128); + + // f64 -> i32 + test_both_cast::(0.0, 0); + test_both_cast::(-0.0, 0); + test_both_cast::(/*0x1.199999999999ap+0*/ f64::from_bits(0x3ff199999999999a), 1); + test_both_cast::( + /*-0x1.199999999999ap+0*/ f64::from_bits(0xbff199999999999a), + -1, + ); + test_both_cast::(1.9, 1); + test_both_cast::(-1.9, -1); + test_both_cast::(1e8, 100_000_000); + test_both_cast::(2147483647.0, 2147483647); + test_both_cast::(-2147483648.0, -2147483648); + // unrepresentable casts + assert_eq::(2147483648.0f64 as i32, i32::MAX); + assert_eq::(-2147483649.0f64 as i32, i32::MIN); + + // f64 -> i64 + test_both_cast::(0.0, 0); + test_both_cast::(-0.0, 0); + test_both_cast::(/*0x0.0000000000001p-1022*/ f64::from_bits(0x1), 0); + test_both_cast::( + /*-0x0.0000000000001p-1022*/ f64::from_bits(0x8000000000000001), + 0, + ); + test_both_cast::(/*0x1.199999999999ap+0*/ f64::from_bits(0x3ff199999999999a), 1); + test_both_cast::( + /*-0x1.199999999999ap+0*/ f64::from_bits(0xbff199999999999a), + -1, + ); + test_both_cast::(5.0, 5); + test_both_cast::(5.9, 5); + test_both_cast::(-5.0, -5); + test_both_cast::(-5.9, -5); + test_both_cast::(4294967296.0, 4294967296); + test_both_cast::(-4294967296.0, -4294967296); + test_both_cast::(9223372036854774784.0, 9223372036854774784); + test_both_cast::(-9223372036854775808.0, -9223372036854775808); + // unrepresentable casts + assert_eq::(9223372036854775808.0f64 as i64, i64::MAX); + assert_eq::(-9223372036854777856.0f64 as i64, i64::MIN); + assert_eq::(f64::MAX as i64, i64::MAX); + assert_eq::(f64::MIN as i64, i64::MIN); + assert_eq::(f64::INFINITY as i64, i64::MAX); + assert_eq::(f64::NEG_INFINITY as i64, i64::MIN); + assert_eq::(f64::NAN as i64, 0); + assert_eq::((-f64::NAN) as i64, 0); + + // f64 -> u64 + test_both_cast::(0.0, 0); + test_both_cast::(-0.0, 0); + test_both_cast::(-0.99999999999, 0); + test_both_cast::(5.0, 5); + test_both_cast::(1e16, 10000000000000000); + test_both_cast::((u64::MAX - 1024) as f64, u64::MAX - 2047); // rounding loss + test_both_cast::(9223372036854775808.0, 9223372036854775808); + // unrepresentable casts + assert_eq::(-5.0f64 as u64, 0); + assert_eq::((u64::MAX - 1023) as f64 as u64, u64::MAX); // rounds up and then becomes unrepresentable + assert_eq::(18446744073709551616.0f64 as u64, u64::MAX); + assert_eq::(f64::MAX as u64, u64::MAX); + assert_eq::(f64::MIN as u64, 0); + assert_eq::(f64::INFINITY as u64, u64::MAX); + assert_eq::(f64::NEG_INFINITY as u64, 0); + assert_eq::(f64::NAN as u64, 0); + assert_eq::((-f64::NAN) as u64, 0); + + // f64 -> i128 + assert_eq::(f64::MAX as i128, i128::MAX); + assert_eq::(f64::MIN as i128, i128::MIN); + + // f64 -> u128 + assert_eq::(f64::MAX as u128, u128::MAX); + assert_eq::(f64::MIN as u128, 0); + + // int -> f32 + assert_eq::(127i8 as f32, 127.0); + assert_eq::(2147483647i32 as f32, 2147483648.0); + assert_eq::((-2147483648i32) as f32, -2147483648.0); + assert_eq::(1234567890i32 as f32, /*0x1.26580cp+30*/ f32::from_bits(0x4e932c06)); + assert_eq::(16777217i32 as f32, 16777216.0); + assert_eq::((-16777217i32) as f32, -16777216.0); + assert_eq::(16777219i32 as f32, 16777220.0); + assert_eq::((-16777219i32) as f32, -16777220.0); + assert_eq::( + 0x7fffff4000000001i64 as f32, + /*0x1.fffffep+62*/ f32::from_bits(0x5effffff), + ); + assert_eq::( + 0x8000004000000001u64 as i64 as f32, + /*-0x1.fffffep+62*/ f32::from_bits(0xdeffffff), + ); + assert_eq::( + 0x0020000020000001i64 as f32, + /*0x1.000002p+53*/ f32::from_bits(0x5a000001), + ); + assert_eq::( + 0xffdfffffdfffffffu64 as i64 as f32, + /*-0x1.000002p+53*/ f32::from_bits(0xda000001), + ); + assert_eq::(i128::MIN as f32, -170141183460469231731687303715884105728.0f32); + assert_eq::(u128::MAX as f32, f32::INFINITY); // saturation + + // int -> f64 + assert_eq::(127i8 as f64, 127.0); + assert_eq::(i16::MIN as f64, -32768.0f64); + assert_eq::(2147483647i32 as f64, 2147483647.0); + assert_eq::(-2147483648i32 as f64, -2147483648.0); + assert_eq::(987654321i32 as f64, 987654321.0); + assert_eq::(9223372036854775807i64 as f64, 9223372036854775807.0); + assert_eq::(-9223372036854775808i64 as f64, -9223372036854775808.0); + assert_eq::(4669201609102990i64 as f64, 4669201609102990.0); // Feigenbaum (?) + assert_eq::(9007199254740993i64 as f64, 9007199254740992.0); + assert_eq::(-9007199254740993i64 as f64, -9007199254740992.0); + assert_eq::(9007199254740995i64 as f64, 9007199254740996.0); + assert_eq::(-9007199254740995i64 as f64, -9007199254740996.0); + assert_eq::(u128::MAX as f64, 340282366920938463463374607431768211455.0f64); // even that fits... + + // f32 -> f64 + assert_eq::((0.0f32 as f64).to_bits(), 0.0f64.to_bits()); + assert_eq::(((-0.0f32) as f64).to_bits(), (-0.0f64).to_bits()); + assert_eq::(5.0f32 as f64, 5.0f64); + assert_eq::( + /*0x1p-149*/ f32::from_bits(0x1) as f64, + /*0x1p-149*/ f64::from_bits(0x36a0000000000000), + ); + assert_eq::( + /*-0x1p-149*/ f32::from_bits(0x80000001) as f64, + /*-0x1p-149*/ f64::from_bits(0xb6a0000000000000), + ); + assert_eq::( + /*0x1.fffffep+127*/ f32::from_bits(0x7f7fffff) as f64, + /*0x1.fffffep+127*/ f64::from_bits(0x47efffffe0000000), + ); + assert_eq::( + /*-0x1.fffffep+127*/ (-f32::from_bits(0x7f7fffff)) as f64, + /*-0x1.fffffep+127*/ -f64::from_bits(0x47efffffe0000000), + ); + assert_eq::( + /*0x1p-119*/ f32::from_bits(0x4000000) as f64, + /*0x1p-119*/ f64::from_bits(0x3880000000000000), + ); + assert_eq::( + /*0x1.8f867ep+125*/ f32::from_bits(0x7e47c33f) as f64, + 6.6382536710104395e+37, + ); + assert_eq::(f32::INFINITY as f64, f64::INFINITY); + assert_eq::(f32::NEG_INFINITY as f64, f64::NEG_INFINITY); + + // f64 -> f32 + assert_eq::((0.0f64 as f32).to_bits(), 0.0f32.to_bits()); + assert_eq::(((-0.0f64) as f32).to_bits(), (-0.0f32).to_bits()); + assert_eq::(5.0f64 as f32, 5.0f32); + assert_eq::(/*0x0.0000000000001p-1022*/ f64::from_bits(0x1) as f32, 0.0); + assert_eq::(/*-0x0.0000000000001p-1022*/ (-f64::from_bits(0x1)) as f32, -0.0); + assert_eq::( + /*0x1.fffffe0000000p-127*/ f64::from_bits(0x380fffffe0000000) as f32, + /*0x1p-149*/ f32::from_bits(0x800000), + ); + assert_eq::( + /*0x1.4eae4f7024c7p+108*/ f64::from_bits(0x46b4eae4f7024c70) as f32, + /*0x1.4eae5p+108*/ f32::from_bits(0x75a75728), + ); + assert_eq::(f64::MAX as f32, f32::INFINITY); + assert_eq::(f64::MIN as f32, f32::NEG_INFINITY); + assert_eq::(f64::INFINITY as f32, f32::INFINITY); + assert_eq::(f64::NEG_INFINITY as f32, f32::NEG_INFINITY); +} + +fn ops() { + // f32 min/max + assert_eq((1.0 as f32).max(-1.0), 1.0); + assert_eq((1.0 as f32).min(-1.0), -1.0); + assert_eq(f32::NAN.min(9.0), 9.0); + assert_eq(f32::NAN.max(-9.0), -9.0); + assert_eq((9.0 as f32).min(f32::NAN), 9.0); + assert_eq((-9.0 as f32).max(f32::NAN), -9.0); + + // f64 min/max + assert_eq((1.0 as f64).max(-1.0), 1.0); + assert_eq((1.0 as f64).min(-1.0), -1.0); + assert_eq(f64::NAN.min(9.0), 9.0); + assert_eq(f64::NAN.max(-9.0), -9.0); + assert_eq((9.0 as f64).min(f64::NAN), 9.0); + assert_eq((-9.0 as f64).max(f64::NAN), -9.0); + + // f32 copysign + assert_eq(3.5_f32.copysign(0.42), 3.5_f32); + assert_eq(3.5_f32.copysign(-0.42), -3.5_f32); + assert_eq((-3.5_f32).copysign(0.42), 3.5_f32); + assert_eq((-3.5_f32).copysign(-0.42), -3.5_f32); + assert!(f32::NAN.copysign(1.0).is_nan()); + + // f64 copysign + assert_eq(3.5_f64.copysign(0.42), 3.5_f64); + assert_eq(3.5_f64.copysign(-0.42), -3.5_f64); + assert_eq((-3.5_f64).copysign(0.42), 3.5_f64); + assert_eq((-3.5_f64).copysign(-0.42), -3.5_f64); + assert!(f64::NAN.copysign(1.0).is_nan()); +} + +/// Tests taken from rustc test suite. +/// + +macro_rules! test { + ($val:expr, $src_ty:ident -> $dest_ty:ident, $expected:expr) => ( + // black_box disables constant evaluation to test run-time conversions: + assert_eq!(black_box::<$src_ty>($val) as $dest_ty, $expected, + "run-time {} -> {}", stringify!($src_ty), stringify!($dest_ty)); + + { + const X: $src_ty = $val; + const Y: $dest_ty = X as $dest_ty; + assert_eq!(Y, $expected, + "const eval {} -> {}", stringify!($src_ty), stringify!($dest_ty)); + } + ); + + ($fval:expr, f* -> $ity:ident, $ival:expr) => ( + test!($fval, f32 -> $ity, $ival); + test!($fval, f64 -> $ity, $ival); + ) +} + +macro_rules! common_fptoi_tests { + ($fty:ident -> $($ity:ident)+) => ({ $( + test!($fty::NAN, $fty -> $ity, 0); + test!($fty::INFINITY, $fty -> $ity, $ity::MAX); + test!($fty::NEG_INFINITY, $fty -> $ity, $ity::MIN); + // These two tests are not solely float->int tests, in particular the latter relies on + // `u128::MAX as f32` not being UB. But that's okay, since this file tests int->float + // as well, the test is just slightly misplaced. + test!($ity::MIN as $fty, $fty -> $ity, $ity::MIN); + test!($ity::MAX as $fty, $fty -> $ity, $ity::MAX); + test!(0., $fty -> $ity, 0); + test!($fty::MIN_POSITIVE, $fty -> $ity, 0); + test!(-0.9, $fty -> $ity, 0); + test!(1., $fty -> $ity, 1); + test!(42., $fty -> $ity, 42); + )+ }); + + (f* -> $($ity:ident)+) => ({ + common_fptoi_tests!(f32 -> $($ity)+); + common_fptoi_tests!(f64 -> $($ity)+); + }) +} + +macro_rules! fptoui_tests { + ($fty: ident -> $($ity: ident)+) => ({ $( + test!(-0., $fty -> $ity, 0); + test!(-$fty::MIN_POSITIVE, $fty -> $ity, 0); + test!(-0.99999994, $fty -> $ity, 0); + test!(-1., $fty -> $ity, 0); + test!(-100., $fty -> $ity, 0); + test!(#[allow(overflowing_literals)] -1e50, $fty -> $ity, 0); + test!(#[allow(overflowing_literals)] -1e130, $fty -> $ity, 0); + )+ }); + + (f* -> $($ity:ident)+) => ({ + fptoui_tests!(f32 -> $($ity)+); + fptoui_tests!(f64 -> $($ity)+); + }) +} + +fn more_casts() { + common_fptoi_tests!(f* -> i8 i16 i32 i64 u8 u16 u32 u64); + fptoui_tests!(f* -> u8 u16 u32 u64); + common_fptoi_tests!(f* -> i128 u128); + fptoui_tests!(f* -> u128); + + // The following tests cover edge cases for some integer types. + + // # u8 + test!(254., f* -> u8, 254); + test!(256., f* -> u8, 255); + + // # i8 + test!(-127., f* -> i8, -127); + test!(-129., f* -> i8, -128); + test!(126., f* -> i8, 126); + test!(128., f* -> i8, 127); + + // # i32 + // -2147483648. is i32::MIN (exactly) + test!(-2147483648., f* -> i32, i32::MIN); + // 2147483648. is i32::MAX rounded up + test!(2147483648., f32 -> i32, 2147483647); + // With 24 significand bits, floats with magnitude in [2^30 + 1, 2^31] are rounded to + // multiples of 2^7. Therefore, nextDown(round(i32::MAX)) is 2^31 - 128: + test!(2147483520., f32 -> i32, 2147483520); + // Similarly, nextUp(i32::MIN) is i32::MIN + 2^8 and nextDown(i32::MIN) is i32::MIN - 2^7 + test!(-2147483904., f* -> i32, i32::MIN); + test!(-2147483520., f* -> i32, -2147483520); + + // # u32 + // round(MAX) and nextUp(round(MAX)) + test!(4294967040., f* -> u32, 4294967040); + test!(4294967296., f* -> u32, 4294967295); + + // # u128 + // float->int: + test!(f32::MAX, f32 -> u128, 0xffffff00000000000000000000000000); + // nextDown(f32::MAX) = 2^128 - 2 * 2^104 + const SECOND_LARGEST_F32: f32 = 340282326356119256160033759537265639424.; + test!(SECOND_LARGEST_F32, f32 -> u128, 0xfffffe00000000000000000000000000); +} + +fn nan_casts() { + let nan1 = f64::from_bits(0x7FF0_0001_0000_0001u64); + let nan2 = f64::from_bits(0x7FF0_0000_0000_0001u64); + + assert!(nan1.is_nan()); + assert!(nan2.is_nan()); + + let nan1_32 = nan1 as f32; + let nan2_32 = nan2 as f32; + + assert!(nan1_32.is_nan()); + assert!(nan2_32.is_nan()); +} diff --git a/src/tools/miri/tests/pass/float_fast_math.rs b/src/tools/miri/tests/pass/float_fast_math.rs new file mode 100644 index 0000000000000..52d985667df2d --- /dev/null +++ b/src/tools/miri/tests/pass/float_fast_math.rs @@ -0,0 +1,34 @@ +#![feature(core_intrinsics)] + +use std::intrinsics::{fadd_fast, fdiv_fast, fmul_fast, frem_fast, fsub_fast}; + +#[inline(never)] +pub fn test_operations_f64(a: f64, b: f64) { + // make sure they all map to the correct operation + unsafe { + assert_eq!(fadd_fast(a, b), a + b); + assert_eq!(fsub_fast(a, b), a - b); + assert_eq!(fmul_fast(a, b), a * b); + assert_eq!(fdiv_fast(a, b), a / b); + assert_eq!(frem_fast(a, b), a % b); + } +} + +#[inline(never)] +pub fn test_operations_f32(a: f32, b: f32) { + // make sure they all map to the correct operation + unsafe { + assert_eq!(fadd_fast(a, b), a + b); + assert_eq!(fsub_fast(a, b), a - b); + assert_eq!(fmul_fast(a, b), a * b); + assert_eq!(fdiv_fast(a, b), a / b); + assert_eq!(frem_fast(a, b), a % b); + } +} + +fn main() { + test_operations_f64(1., 2.); + test_operations_f64(10., 5.); + test_operations_f32(11., 2.); + test_operations_f32(10., 15.); +} diff --git a/src/tools/miri/tests/pass/format.rs b/src/tools/miri/tests/pass/format.rs new file mode 100644 index 0000000000000..053cce36130c7 --- /dev/null +++ b/src/tools/miri/tests/pass/format.rs @@ -0,0 +1,4 @@ +fn main() { + println!("Hello {}", 13); + println!("{:0 *mut std::ffi::c_void; + } + + unsafe { + let _ = malloc(0); + std::mem::transmute::(foo)(); + std::intrinsics::r#try( + std::mem::transmute::(try_fn), + std::ptr::null_mut(), + |_, _| unreachable!(), + ); + } +} diff --git a/src/tools/miri/tests/pass/function_calls/exported_symbol.rs b/src/tools/miri/tests/pass/function_calls/exported_symbol.rs new file mode 100644 index 0000000000000..27aee9c883588 --- /dev/null +++ b/src/tools/miri/tests/pass/function_calls/exported_symbol.rs @@ -0,0 +1,79 @@ +#![feature(rustc_attrs)] + +#[no_mangle] +extern "C" fn foo() -> i32 { + -1 +} + +#[export_name = "bar"] +fn bar() -> i32 { + -2 +} + +#[rustc_std_internal_symbol] +fn baz() -> i32 { + -3 +} + +struct AssocFn; + +impl AssocFn { + #[no_mangle] + fn qux() -> i32 { + -4 + } +} + +fn main() { + // Repeat calls to make sure the `Instance` cache is not broken. + for _ in 0..3 { + extern "C" { + fn foo() -> i32; + fn free(_: *mut std::ffi::c_void); + } + + assert_eq!(unsafe { foo() }, -1); + + // `free()` is a built-in shim, so calling it will add ("free", None) to the cache. + // Test that the cache is not broken with ("free", None). + unsafe { free(std::ptr::null_mut()) } + + extern "Rust" { + fn bar() -> i32; + fn baz() -> i32; + fn qux() -> i32; + } + + assert_eq!(unsafe { bar() }, -2); + assert_eq!(unsafe { baz() }, -3); + assert_eq!(unsafe { qux() }, -4); + + #[allow(clashing_extern_declarations)] + { + extern "Rust" { + fn foo() -> i32; + } + + assert_eq!( + unsafe { + std::mem::transmute:: i32, unsafe extern "C" fn() -> i32>(foo)() + }, + -1 + ); + + extern "C" { + fn bar() -> i32; + fn baz() -> i32; + fn qux() -> i32; + } + + unsafe { + let transmute = + |f| std::mem::transmute:: i32, unsafe fn() -> i32>(f); + assert_eq!(transmute(bar)(), -2); + assert_eq!(transmute(baz)(), -3); + assert_eq!(transmute(qux)(), -4); + } + } + } +} diff --git a/src/tools/miri/tests/pass/function_pointers.rs b/src/tools/miri/tests/pass/function_pointers.rs new file mode 100644 index 0000000000000..b66826e3fcdfb --- /dev/null +++ b/src/tools/miri/tests/pass/function_pointers.rs @@ -0,0 +1,89 @@ +use std::mem; + +trait Answer { + fn answer() -> Self; +} + +impl Answer for i32 { + fn answer() -> i32 { + 42 + } +} + +// A generic function, to make its address unstable +fn f() -> T { + Answer::answer() +} + +fn g(i: i32) -> i32 { + i * 42 +} + +fn h(i: i32, j: i32) -> i32 { + j * i * 7 +} + +fn return_fn_ptr(f: fn() -> i32) -> fn() -> i32 { + f +} + +fn call_fn_ptr() -> i32 { + return_fn_ptr(f)() +} + +fn indirect i32>(f: F) -> i32 { + f() +} +fn indirect_mut i32>(mut f: F) -> i32 { + f() +} +fn indirect_once i32>(f: F) -> i32 { + f() +} + +fn indirect2 i32>(f: F) -> i32 { + f(10) +} +fn indirect_mut2 i32>(mut f: F) -> i32 { + f(10) +} +fn indirect_once2 i32>(f: F) -> i32 { + f(10) +} + +fn indirect3 i32>(f: F) -> i32 { + f(10, 3) +} +fn indirect_mut3 i32>(mut f: F) -> i32 { + f(10, 3) +} +fn indirect_once3 i32>(f: F) -> i32 { + f(10, 3) +} + +fn main() { + assert_eq!(call_fn_ptr(), 42); + assert_eq!(indirect(f), 42); + assert_eq!(indirect_mut(f), 42); + assert_eq!(indirect_once(f), 42); + assert_eq!(indirect2(g), 420); + assert_eq!(indirect_mut2(g), 420); + assert_eq!(indirect_once2(g), 420); + assert_eq!(indirect3(h), 210); + assert_eq!(indirect_mut3(h), 210); + assert_eq!(indirect_once3(h), 210); + let g = f as fn() -> i32; + assert!(return_fn_ptr(g) == g); + assert!(return_fn_ptr(g) as unsafe fn() -> i32 == g as fn() -> i32 as unsafe fn() -> i32); + assert!(return_fn_ptr(f) != f); + + // Any non-null value is okay for function pointers. + unsafe { + let _x: fn() = mem::transmute(1usize); + let mut b = Box::new(42u8); + let ptr = &mut *b as *mut u8; + drop(b); + let _x: fn() = mem::transmute(ptr); + let _x: fn() = mem::transmute(ptr.wrapping_offset(1)); + } +} diff --git a/src/tools/miri/tests/pass/generator.rs b/src/tools/miri/tests/pass/generator.rs new file mode 100644 index 0000000000000..06f48666c557f --- /dev/null +++ b/src/tools/miri/tests/pass/generator.rs @@ -0,0 +1,210 @@ +#![feature(generators, generator_trait, never_type)] + +use std::fmt::Debug; +use std::mem::ManuallyDrop; +use std::ops::{ + Generator, + GeneratorState::{self, *}, +}; +use std::pin::Pin; +use std::ptr; +use std::sync::atomic::{AtomicUsize, Ordering}; + +fn basic() { + fn finish(mut amt: usize, mut t: T) -> T::Return + where + T: Generator, + { + // We are not moving the `t` around until it gets dropped, so this is okay. + let mut t = unsafe { Pin::new_unchecked(&mut t) }; + loop { + let state = t.as_mut().resume(()); + // Test if the generator is valid (according to type invariants). + let _ = unsafe { ManuallyDrop::new(ptr::read(t.as_mut().get_unchecked_mut())) }; + match state { + GeneratorState::Yielded(y) => { + amt -= y; + } + GeneratorState::Complete(ret) => { + assert_eq!(amt, 0); + return ret; + } + } + } + } + + enum Never {} + fn never() -> Never { + panic!() + } + + finish(1, || yield 1); + + finish(3, || { + let mut x = 0; + yield 1; + x += 1; + yield 1; + x += 1; + yield 1; + assert_eq!(x, 2); + }); + + finish(7 * 8 / 2, || { + for i in 0..8 { + yield i; + } + }); + + finish(1, || { + if true { + yield 1; + } else { + } + }); + + finish(1, || { + if false { + } else { + yield 1; + } + }); + + finish(2, || { + if { + yield 1; + false + } { + yield 1; + panic!() + } + yield 1; + }); + + // also test a self-referential generator + assert_eq!( + finish(5, || { + let mut x = Box::new(5); + let y = &mut *x; + *y = 5; + yield *y; + *y = 10; + *x + }), + 10 + ); + + let b = true; + finish(1, || { + yield 1; + if b { + return; + } + #[allow(unused)] + let x = never(); + #[allow(unreachable_code)] + yield 2; + drop(x); + }); + + finish(3, || { + yield 1; + #[allow(unreachable_code)] + let _x: (String, !) = (String::new(), { + yield 2; + return; + }); + }); +} + +fn smoke_resume_arg() { + fn drain + Unpin, R, Y>( + gen: &mut G, + inout: Vec<(R, GeneratorState)>, + ) where + Y: Debug + PartialEq, + G::Return: Debug + PartialEq, + { + let mut gen = Pin::new(gen); + + for (input, out) in inout { + assert_eq!(gen.as_mut().resume(input), out); + // Test if the generator is valid (according to type invariants). + let _ = unsafe { ManuallyDrop::new(ptr::read(gen.as_mut().get_unchecked_mut())) }; + } + } + + static DROPS: AtomicUsize = AtomicUsize::new(0); + + #[derive(Debug, PartialEq)] + struct DropMe; + + impl Drop for DropMe { + fn drop(&mut self) { + DROPS.fetch_add(1, Ordering::SeqCst); + } + } + + fn expect_drops(expected_drops: usize, f: impl FnOnce() -> T) -> T { + DROPS.store(0, Ordering::SeqCst); + + let res = f(); + + let actual_drops = DROPS.load(Ordering::SeqCst); + assert_eq!(actual_drops, expected_drops); + res + } + + drain( + &mut |mut b| { + while b != 0 { + b = yield (b + 1); + } + -1 + }, + vec![(1, Yielded(2)), (-45, Yielded(-44)), (500, Yielded(501)), (0, Complete(-1))], + ); + + expect_drops(2, || drain(&mut |a| yield a, vec![(DropMe, Yielded(DropMe))])); + + expect_drops(6, || { + drain( + &mut |a| yield yield a, + vec![(DropMe, Yielded(DropMe)), (DropMe, Yielded(DropMe)), (DropMe, Complete(DropMe))], + ) + }); + + #[allow(unreachable_code)] + expect_drops(2, || drain(&mut |a| yield return a, vec![(DropMe, Complete(DropMe))])); + + expect_drops(2, || { + drain( + &mut |a: DropMe| { + if false { yield () } else { a } + }, + vec![(DropMe, Complete(DropMe))], + ) + }); + + expect_drops(4, || { + drain( + #[allow(unused_assignments, unused_variables)] + &mut |mut a: DropMe| { + a = yield; + a = yield; + a = yield; + }, + vec![ + (DropMe, Yielded(())), + (DropMe, Yielded(())), + (DropMe, Yielded(())), + (DropMe, Complete(())), + ], + ) + }); +} + +fn main() { + basic(); + smoke_resume_arg(); +} diff --git a/src/tools/miri/tests/pass/getpid.rs b/src/tools/miri/tests/pass/getpid.rs new file mode 100644 index 0000000000000..733545462ebc0 --- /dev/null +++ b/src/tools/miri/tests/pass/getpid.rs @@ -0,0 +1,9 @@ +//@compile-flags: -Zmiri-disable-isolation + +fn getpid() -> u32 { + std::process::id() +} + +fn main() { + getpid(); +} diff --git a/src/tools/miri/tests/pass/global_allocator.rs b/src/tools/miri/tests/pass/global_allocator.rs new file mode 100644 index 0000000000000..24a56c663f060 --- /dev/null +++ b/src/tools/miri/tests/pass/global_allocator.rs @@ -0,0 +1,41 @@ +#![feature(allocator_api, slice_ptr_get)] + +use std::alloc::{Allocator as _, Global, GlobalAlloc, Layout, System}; + +#[global_allocator] +static ALLOCATOR: Allocator = Allocator; + +struct Allocator; + +unsafe impl GlobalAlloc for Allocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + // use specific size to avoid getting triggered by rt + if layout.size() == 123 { + println!("Allocated!") + } + + System.alloc(layout) + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + if layout.size() == 123 { + println!("Dellocated!") + } + + System.dealloc(ptr, layout) + } +} + +fn main() { + // Only okay because we explicitly set a global allocator that uses the system allocator! + let l = Layout::from_size_align(123, 1).unwrap(); + let ptr = Global.allocate(l).unwrap().as_non_null_ptr(); // allocating with Global... + unsafe { + System.deallocate(ptr, l); + } // ... and deallocating with System. + + let ptr = System.allocate(l).unwrap().as_non_null_ptr(); // allocating with System... + unsafe { + Global.deallocate(ptr, l); + } // ... and deallocating with Global. +} diff --git a/src/tools/miri/tests/pass/global_allocator.stdout b/src/tools/miri/tests/pass/global_allocator.stdout new file mode 100644 index 0000000000000..411a4cdd1467e --- /dev/null +++ b/src/tools/miri/tests/pass/global_allocator.stdout @@ -0,0 +1,2 @@ +Allocated! +Dellocated! diff --git a/src/tools/miri/tests/pass/hashmap.rs b/src/tools/miri/tests/pass/hashmap.rs new file mode 100644 index 0000000000000..29ddd6c59a1a6 --- /dev/null +++ b/src/tools/miri/tests/pass/hashmap.rs @@ -0,0 +1,40 @@ +use std::collections::HashMap; +use std::hash::BuildHasher; + +// Gather all references from a mutable iterator and make sure Miri notices if +// using them is dangerous. +fn test_all_refs<'a, T: 'a>(dummy: &mut T, iter: impl Iterator) { + // Gather all those references. + let mut refs: Vec<&mut T> = iter.collect(); + // Use them all. Twice, to be sure we got all interleavings. + for r in refs.iter_mut() { + std::mem::swap(dummy, r); + } + for r in refs { + std::mem::swap(dummy, r); + } +} + +fn smoketest_map(mut map: HashMap) { + map.insert(0, 0); + assert_eq!(map.values().fold(0, |x, y| x + y), 0); + + let num = 25; + for i in 1..num { + map.insert(i, i); + } + assert_eq!(map.values().fold(0, |x, y| x + y), num * (num - 1) / 2); // check the right things are in the table now + + // Inserting again replaces the existing entries + for i in 0..num { + map.insert(i, num - 1 - i); + } + assert_eq!(map.values().fold(0, |x, y| x + y), num * (num - 1) / 2); + + test_all_refs(&mut 13, map.values_mut()); +} + +fn main() { + // hashbrown uses Miri on its own CI; we just do a smoketest here. + smoketest_map(HashMap::new()); +} diff --git a/src/tools/miri/tests/pass/heap.rs b/src/tools/miri/tests/pass/heap.rs new file mode 100644 index 0000000000000..44537e74b5a44 --- /dev/null +++ b/src/tools/miri/tests/pass/heap.rs @@ -0,0 +1,34 @@ +#![feature(box_syntax)] + +fn make_box() -> Box<(i16, i16)> { + Box::new((1, 2)) +} + +fn make_box_syntax() -> Box<(i16, i16)> { + box (1, 2) +} + +fn allocate_reallocate() { + let mut s = String::new(); + + // 6 byte heap alloc (__rust_allocate) + s.push_str("foobar"); + assert_eq!(s.len(), 6); + assert_eq!(s.capacity(), 8); + + // heap size doubled to 12 (__rust_reallocate) + s.push_str("baz"); + assert_eq!(s.len(), 9); + assert_eq!(s.capacity(), 16); + + // heap size reduced to 9 (__rust_reallocate) + s.shrink_to_fit(); + assert_eq!(s.len(), 9); + assert_eq!(s.capacity(), 9); +} + +fn main() { + assert_eq!(*make_box(), (1, 2)); + assert_eq!(*make_box_syntax(), (1, 2)); + allocate_reallocate(); +} diff --git a/src/tools/miri/tests/pass/heap_allocator.rs b/src/tools/miri/tests/pass/heap_allocator.rs new file mode 100644 index 0000000000000..2c38dcb49f1ce --- /dev/null +++ b/src/tools/miri/tests/pass/heap_allocator.rs @@ -0,0 +1,127 @@ +#![feature(allocator_api, slice_ptr_get)] + +use std::alloc::{Allocator, Global, Layout, System}; +use std::ptr::NonNull; +use std::slice; + +fn check_alloc(allocator: T) { + unsafe { + for &align in &[4, 8, 16, 32] { + let layout_20 = Layout::from_size_align(20, align).unwrap(); + let layout_40 = Layout::from_size_align(40, 4 * align).unwrap(); + let layout_10 = Layout::from_size_align(10, align / 2).unwrap(); + + for _ in 0..32 { + let a = allocator.allocate(layout_20).unwrap().as_non_null_ptr(); + assert_eq!( + a.as_ptr() as usize % layout_20.align(), + 0, + "pointer is incorrectly aligned", + ); + allocator.deallocate(a, layout_20); + } + + let p1 = allocator.allocate_zeroed(layout_20).unwrap().as_non_null_ptr(); + assert_eq!( + p1.as_ptr() as usize % layout_20.align(), + 0, + "pointer is incorrectly aligned", + ); + assert_eq!(*p1.as_ptr(), 0); + + // old size < new size + let p2 = allocator.grow(p1, layout_20, layout_40).unwrap().as_non_null_ptr(); + assert_eq!( + p2.as_ptr() as usize % layout_40.align(), + 0, + "pointer is incorrectly aligned", + ); + let slice = slice::from_raw_parts(p2.as_ptr(), 20); + assert_eq!(&slice, &[0_u8; 20]); + + // old size == new size + let p3 = allocator.grow(p2, layout_40, layout_40).unwrap().as_non_null_ptr(); + assert_eq!( + p3.as_ptr() as usize % layout_40.align(), + 0, + "pointer is incorrectly aligned", + ); + let slice = slice::from_raw_parts(p3.as_ptr(), 20); + assert_eq!(&slice, &[0_u8; 20]); + + // old size > new size + let p4 = allocator.shrink(p3, layout_40, layout_10).unwrap().as_non_null_ptr(); + assert_eq!( + p4.as_ptr() as usize % layout_10.align(), + 0, + "pointer is incorrectly aligned", + ); + let slice = slice::from_raw_parts(p4.as_ptr(), 10); + assert_eq!(&slice, &[0_u8; 10]); + + allocator.deallocate(p4, layout_10); + } + } +} + +fn check_align_requests(allocator: T) { + #[rustfmt::skip] // https://github.com/rust-lang/rustfmt/issues/3255 + for &size in &[2, 8, 64] { // size less than and bigger than alignment + for &align in &[4, 8, 16, 32] { // Be sure to cover less than and bigger than `MIN_ALIGN` for all architectures + let iterations = 32; + unsafe { + let pointers: Vec<_> = (0..iterations) + .map(|_| { + allocator + .allocate(Layout::from_size_align(size, align).unwrap()) + .unwrap() + .as_non_null_ptr() + }) + .collect(); + for &ptr in &pointers { + assert_eq!( + (ptr.as_ptr() as usize) % align, + 0, + "Got a pointer less aligned than requested", + ) + } + + // Clean up. + for &ptr in &pointers { + allocator.deallocate(ptr, Layout::from_size_align(size, align).unwrap()) + } + } + } + }; +} + +fn global_to_box() { + type T = [i32; 4]; + let l = Layout::new::(); + // allocate manually with global allocator, then turn into Box and free there + unsafe { + let ptr = Global.allocate(l).unwrap().as_non_null_ptr().as_ptr() as *mut T; + let b = Box::from_raw(ptr); + drop(b); + } +} + +fn box_to_global() { + type T = [i32; 4]; + let l = Layout::new::(); + // allocate with the Box, then deallocate manually with global allocator + unsafe { + let b = Box::new(T::default()); + let ptr = Box::into_raw(b); + Global.deallocate(NonNull::new(ptr as *mut u8).unwrap(), l); + } +} + +fn main() { + check_alloc(System); + check_alloc(Global); + check_align_requests(System); + check_align_requests(Global); + global_to_box(); + box_to_global(); +} diff --git a/src/tools/miri/tests/pass/hello.rs b/src/tools/miri/tests/pass/hello.rs new file mode 100644 index 0000000000000..e7a11a969c037 --- /dev/null +++ b/src/tools/miri/tests/pass/hello.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/src/tools/miri/tests/pass/hello.stdout b/src/tools/miri/tests/pass/hello.stdout new file mode 100644 index 0000000000000..af5626b4a114a --- /dev/null +++ b/src/tools/miri/tests/pass/hello.stdout @@ -0,0 +1 @@ +Hello, world! diff --git a/src/tools/miri/tests/pass/hide_stdout.rs b/src/tools/miri/tests/pass/hide_stdout.rs new file mode 100644 index 0000000000000..cfd05a8396cb5 --- /dev/null +++ b/src/tools/miri/tests/pass/hide_stdout.rs @@ -0,0 +1,6 @@ +//@compile-flags: -Zmiri-mute-stdout-stderr + +fn main() { + println!("print to stdout"); + eprintln!("print to stderr"); +} diff --git a/src/tools/miri/tests/pass/integer-ops.rs b/src/tools/miri/tests/pass/integer-ops.rs new file mode 100644 index 0000000000000..724be9efc9f82 --- /dev/null +++ b/src/tools/miri/tests/pass/integer-ops.rs @@ -0,0 +1,205 @@ +//@compile-flags: -Coverflow-checks=off +#![feature(int_log)] +#![allow(arithmetic_overflow)] + +pub fn main() { + // This tests that we do (not) do sign extension properly when loading integers + assert_eq!(u32::MAX as i64, 4294967295); + assert_eq!(i32::MIN as i64, -2147483648); + + assert_eq!(i8::MAX, 127); + assert_eq!(i8::MIN, -128); + + // Shifts with negative offsets are subtle. + assert_eq!(13 << -2i8, 13 << 254); + assert_eq!(13 << i8::MIN, 13); + assert_eq!(13 << -1i16, 13 << u16::MAX); + assert_eq!(13 << i16::MIN, 13); + assert_eq!(13i128 << -2i8, 13i128 << 254); + assert_eq!(13i128 << i8::MIN, 13); + assert_eq!(13i128 << -1i16, 13i128 << u16::MAX); + assert_eq!(13i128 << i16::MIN, 13); + + assert_eq!(i32::from_str_radix("A", 16), Ok(10)); + + let n = -0b1000_0000i8; + assert_eq!(n.count_ones(), 1); + + let n = -0b1000_0000i8; + assert_eq!(n.count_zeros(), 7); + + let n = -1i16; + assert_eq!(n.leading_zeros(), 0); + + let n = -4i8; + assert_eq!(n.trailing_zeros(), 2); + + let n = 0x0123456789ABCDEFi64; + let m = -0x76543210FEDCBA99i64; + assert_eq!(n.rotate_left(32), m); + + let n = 0x0123456789ABCDEFi64; + let m = -0xFEDCBA987654322i64; + assert_eq!(n.rotate_right(4), m); + + let n = 0x0123456789ABCDEFi64; + let m = -0x1032547698BADCFFi64; + assert_eq!(n.swap_bytes(), m); + + let n = 0x0123456789ABCDEFi64; + if cfg!(target_endian = "big") { + assert_eq!(i64::from_be(n), n) + } else { + assert_eq!(i64::from_be(n), n.swap_bytes()) + } + + let n = 0x0123456789ABCDEFi64; + if cfg!(target_endian = "little") { + assert_eq!(i64::from_le(n), n) + } else { + assert_eq!(i64::from_le(n), n.swap_bytes()) + } + + let n = 0x0123456789ABCDEFi64; + if cfg!(target_endian = "big") { + assert_eq!(n.to_be(), n) + } else { + assert_eq!(n.to_be(), n.swap_bytes()) + } + + let n = 0x0123456789ABCDEFi64; + if cfg!(target_endian = "little") { + assert_eq!(n.to_le(), n) + } else { + assert_eq!(n.to_le(), n.swap_bytes()) + } + + assert_eq!(7i16.checked_add(32760), Some(32767)); + assert_eq!(8i16.checked_add(32760), None); + + assert_eq!((-127i8).checked_sub(1), Some(-128)); + assert_eq!((-128i8).checked_sub(1), None); + + assert_eq!(6i8.checked_mul(21), Some(126)); + assert_eq!(6i8.checked_mul(22), None); + + assert_eq!((-127i8).checked_div(-1), Some(127)); + assert_eq!((-128i8).checked_div(-1), None); + assert_eq!((1i8).checked_div(0), None); + + assert_eq!(5i32.checked_rem(2), Some(1)); + assert_eq!(5i32.checked_rem(0), None); + assert_eq!(i32::MIN.checked_rem(-1), None); + + assert_eq!(5i32.checked_neg(), Some(-5)); + assert_eq!(i32::MIN.checked_neg(), None); + + assert_eq!(0x10i32.checked_shl(4), Some(0x100)); + assert_eq!(0x10i32.checked_shl(33), None); + + assert_eq!(0x10i32.checked_shr(4), Some(0x1)); + assert_eq!(0x10i32.checked_shr(33), None); + + assert_eq!((-5i32).checked_abs(), Some(5)); + assert_eq!(i32::MIN.checked_abs(), None); + + assert_eq!(100i8.saturating_add(1), 101); + assert_eq!(100i8.saturating_add(127), 127); + + assert_eq!(100i8.saturating_sub(127), -27); + assert_eq!((-100i8).saturating_sub(127), -128); + + assert_eq!(100i32.saturating_mul(127), 12700); + assert_eq!((1i32 << 23).saturating_mul(1 << 23), i32::MAX); + assert_eq!((-1i32 << 23).saturating_mul(1 << 23), i32::MIN); + + assert_eq!(100i8.wrapping_add(27), 127); + assert_eq!(100i8.wrapping_add(127), -29); + + assert_eq!(0i8.wrapping_sub(127), -127); + assert_eq!((-2i8).wrapping_sub(127), 127); + + assert_eq!(10i8.wrapping_mul(12), 120); + assert_eq!(11i8.wrapping_mul(12), -124); + + assert_eq!(100u8.wrapping_div(10), 10); + assert_eq!((-128i8).wrapping_div(-1), -128); + + assert_eq!(100i8.wrapping_rem(10), 0); + assert_eq!((-128i8).wrapping_rem(-1), 0); + + assert_eq!(i32::MIN.wrapping_div(-1), i32::MIN); + assert_eq!(i32::MIN.wrapping_rem(-1), 0); + + assert_eq!(100i8.wrapping_neg(), -100); + assert_eq!((-128i8).wrapping_neg(), -128); + + assert_eq!((-1i8).wrapping_shl(7), -128); + assert_eq!((-1i8).wrapping_shl(8), -1); + + assert_eq!((-128i8).wrapping_shr(7), -1); + assert_eq!((-128i8).wrapping_shr(8), -128); + + assert_eq!(100i8.wrapping_abs(), 100); + assert_eq!((-100i8).wrapping_abs(), 100); + assert_eq!((-128i8).wrapping_abs(), -128); + assert_eq!((-128i8).wrapping_abs() as u8, 128); + + assert_eq!(5i32.overflowing_add(2), (7, false)); + assert_eq!(i32::MAX.overflowing_add(1), (i32::MIN, true)); + + assert_eq!(5i32.overflowing_sub(2), (3, false)); + assert_eq!(i32::MIN.overflowing_sub(1), (i32::MAX, true)); + + assert_eq!(5i32.overflowing_mul(2), (10, false)); + assert_eq!(1_000_000_000i32.overflowing_mul(10), (1410065408, true)); + + assert_eq!(5i32.overflowing_div(2), (2, false)); + assert_eq!(i32::MIN.overflowing_div(-1), (i32::MIN, true)); + + assert_eq!(5i32.overflowing_rem(2), (1, false)); + assert_eq!(i32::MIN.overflowing_rem(-1), (0, true)); + + assert_eq!(2i32.overflowing_neg(), (-2, false)); + assert_eq!(i32::MIN.overflowing_neg(), (i32::MIN, true)); + + assert_eq!(0x10i32.overflowing_shl(4), (0x100, false)); + assert_eq!(0x10i32.overflowing_shl(36), (0x100, true)); + + assert_eq!(0x10i32.overflowing_shr(4), (0x1, false)); + assert_eq!(0x10i32.overflowing_shr(36), (0x1, true)); + + assert_eq!(10i8.overflowing_abs(), (10, false)); + assert_eq!((-10i8).overflowing_abs(), (10, false)); + assert_eq!((-128i8).overflowing_abs(), (-128, true)); + + // Logarithms + macro_rules! test_log { + ($type:ident, $max_log2:expr, $max_log10:expr) => { + assert_eq!($type::MIN.checked_ilog2(), None); + assert_eq!($type::MIN.checked_ilog10(), None); + assert_eq!($type::MAX.checked_ilog2(), Some($max_log2)); + assert_eq!($type::MAX.checked_ilog10(), Some($max_log10)); + assert_eq!($type::MAX.ilog2(), $max_log2); + assert_eq!($type::MAX.ilog10(), $max_log10); + }; + } + + test_log!(i8, 6, 2); + test_log!(u8, 7, 2); + test_log!(i16, 14, 4); + test_log!(u16, 15, 4); + test_log!(i32, 30, 9); + test_log!(u32, 31, 9); + test_log!(i64, 62, 18); + test_log!(u64, 63, 19); + test_log!(i128, 126, 38); + test_log!(u128, 127, 38); + + for i in (1..=i16::MAX).step_by(i8::MAX as usize) { + assert_eq!(i.checked_ilog(13), Some((i as f32).log(13.0) as u32)); + } + for i in (1..=u16::MAX).step_by(i8::MAX as usize) { + assert_eq!(i.checked_ilog(13), Some((i as f32).log(13.0) as u32)); + } +} diff --git a/src/tools/miri/tests/pass/intptrcast.rs b/src/tools/miri/tests/pass/intptrcast.rs new file mode 100644 index 0000000000000..e7ff90cb6bf09 --- /dev/null +++ b/src/tools/miri/tests/pass/intptrcast.rs @@ -0,0 +1,163 @@ +//@compile-flags: -Zmiri-permissive-provenance + +use std::mem; + +// This strips provenance +fn transmute_ptr_to_int(x: *const T) -> usize { + unsafe { std::mem::transmute(x) } +} + +fn cast() { + // Some casting-to-int with arithmetic. + let x = &42 as *const i32 as usize; + let y = x * 2; + assert_eq!(y, x + x); + let z = y as u8 as usize; + assert_eq!(z, y % 256); +} + +/// Test usize->ptr cast for dangling and OOB address. +/// That is safe, and thus has to work. +fn cast_dangling() { + let b = Box::new(0); + let x = &*b as *const i32 as usize; + drop(b); + let _val = x as *const i32; + + let b = Box::new(0); + let mut x = &*b as *const i32 as usize; + x += 0x100; + let _val = x as *const i32; +} + +fn format() { + // Pointer string formatting! We can't check the output as it changes when libstd changes, + // but we can make sure Miri does not error. + format!("{:?}", &mut 13 as *mut _); +} + +fn transmute() { + // Check that intptrcast is triggered for explicit casts and that it is consistent with + // transmuting. + let a: *const i32 = &42; + let b = transmute_ptr_to_int(a) as u8; + let c = a as u8; + assert_eq!(b, c); +} + +fn ptr_bitops1() { + let bytes = [0i8, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + let one = bytes.as_ptr().wrapping_offset(1); + let three = bytes.as_ptr().wrapping_offset(3); + let res = (one as usize) | (three as usize); + format!("{}", res); +} + +fn ptr_bitops2() { + let val = 13usize; + let addr = &val as *const _ as usize; + let _val = addr & 13; +} + +fn ptr_eq_dangling() { + let b = Box::new(0); + let x = &*b as *const i32; // soon-to-be dangling + drop(b); + let b = Box::new(0); + let y = &*b as *const i32; // different allocation + // They *could* be equal if memory was reused, but probably are not. + assert!(x != y); +} + +fn ptr_eq_out_of_bounds() { + let b = Box::new(0); + let x = (&*b as *const i32).wrapping_sub(0x800); // out-of-bounds + let b = Box::new(0); + let y = &*b as *const i32; // different allocation + // They *could* be equal (with the right base addresses), but probably are not. + assert!(x != y); +} + +fn ptr_eq_out_of_bounds_null() { + let b = Box::new(0); + let x = (&*b as *const i32).wrapping_sub(0x800); // out-of-bounds + // This *could* be NULL (with the right base address), but probably is not. + assert!(x != std::ptr::null()); +} + +fn ptr_eq_integer() { + let b = Box::new(0); + let x = &*b as *const i32; + // These *could* be equal (with the right base address), but probably are not. + assert!(x != 64 as *const i32); +} + +fn zst_deref_of_dangling() { + let b = Box::new(0); + let addr = &*b as *const _ as usize; + drop(b); + // Now if we cast `addr` to a ptr it might pick up the dangling provenance. + // But if we only do a ZST deref there is no UB here! + let zst = addr as *const (); + let _val = unsafe { *zst }; +} + +fn functions() { + // Roundtrip a few functions through integers. Do this multiple times to make sure this does not + // work by chance. If we did not give unique addresses to ZST allocations -- which fn + // allocations are -- then we might be unable to cast back, or we might call the wrong function! + // Every function gets at most one address so doing a loop would not help... + fn fn0() -> i32 { + 0 + } + fn fn1() -> i32 { + 1 + } + fn fn2() -> i32 { + 2 + } + fn fn3() -> i32 { + 3 + } + fn fn4() -> i32 { + 4 + } + fn fn5() -> i32 { + 5 + } + fn fn6() -> i32 { + 6 + } + fn fn7() -> i32 { + 7 + } + let fns = [ + fn0 as fn() -> i32 as *const () as usize, + fn1 as fn() -> i32 as *const () as usize, + fn2 as fn() -> i32 as *const () as usize, + fn3 as fn() -> i32 as *const () as usize, + fn4 as fn() -> i32 as *const () as usize, + fn5 as fn() -> i32 as *const () as usize, + fn6 as fn() -> i32 as *const () as usize, + fn7 as fn() -> i32 as *const () as usize, + ]; + for (idx, &addr) in fns.iter().enumerate() { + let fun: fn() -> i32 = unsafe { mem::transmute(addr as *const ()) }; + assert_eq!(fun(), idx as i32); + } +} + +fn main() { + cast(); + cast_dangling(); + format(); + transmute(); + ptr_bitops1(); + ptr_bitops2(); + ptr_eq_dangling(); + ptr_eq_out_of_bounds(); + ptr_eq_out_of_bounds_null(); + ptr_eq_integer(); + zst_deref_of_dangling(); + functions(); +} diff --git a/src/tools/miri/tests/pass/intrinsics-integer.rs b/src/tools/miri/tests/pass/intrinsics-integer.rs new file mode 100644 index 0000000000000..546931f6ff875 --- /dev/null +++ b/src/tools/miri/tests/pass/intrinsics-integer.rs @@ -0,0 +1,154 @@ +// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![feature(core_intrinsics)] +use std::intrinsics::*; + +pub fn main() { + unsafe { + [assert_eq!(ctpop(0u8), 0), assert_eq!(ctpop(0i8), 0)]; + [assert_eq!(ctpop(0u16), 0), assert_eq!(ctpop(0i16), 0)]; + [assert_eq!(ctpop(0u32), 0), assert_eq!(ctpop(0i32), 0)]; + [assert_eq!(ctpop(0u64), 0), assert_eq!(ctpop(0i64), 0)]; + + [assert_eq!(ctpop(1u8), 1), assert_eq!(ctpop(1i8), 1)]; + [assert_eq!(ctpop(1u16), 1), assert_eq!(ctpop(1i16), 1)]; + [assert_eq!(ctpop(1u32), 1), assert_eq!(ctpop(1i32), 1)]; + [assert_eq!(ctpop(1u64), 1), assert_eq!(ctpop(1i64), 1)]; + + [assert_eq!(ctpop(10u8), 2), assert_eq!(ctpop(10i8), 2)]; + [assert_eq!(ctpop(10u16), 2), assert_eq!(ctpop(10i16), 2)]; + [assert_eq!(ctpop(10u32), 2), assert_eq!(ctpop(10i32), 2)]; + [assert_eq!(ctpop(10u64), 2), assert_eq!(ctpop(10i64), 2)]; + + [assert_eq!(ctpop(100u8), 3), assert_eq!(ctpop(100i8), 3)]; + [assert_eq!(ctpop(100u16), 3), assert_eq!(ctpop(100i16), 3)]; + [assert_eq!(ctpop(100u32), 3), assert_eq!(ctpop(100i32), 3)]; + [assert_eq!(ctpop(100u64), 3), assert_eq!(ctpop(100i64), 3)]; + + [assert_eq!(ctpop(-1i8 as u8), 8), assert_eq!(ctpop(-1i8), 8)]; + [assert_eq!(ctpop(-1i16 as u16), 16), assert_eq!(ctpop(-1i16), 16)]; + [assert_eq!(ctpop(-1i32 as u32), 32), assert_eq!(ctpop(-1i32), 32)]; + [assert_eq!(ctpop(-1i64 as u64), 64), assert_eq!(ctpop(-1i64), 64)]; + + [assert_eq!(ctlz(0u8), 8), assert_eq!(ctlz(0i8), 8)]; + [assert_eq!(ctlz(0u16), 16), assert_eq!(ctlz(0i16), 16)]; + [assert_eq!(ctlz(0u32), 32), assert_eq!(ctlz(0i32), 32)]; + [assert_eq!(ctlz(0u64), 64), assert_eq!(ctlz(0i64), 64)]; + + [assert_eq!(ctlz(1u8), 7), assert_eq!(ctlz(1i8), 7)]; + [assert_eq!(ctlz(1u16), 15), assert_eq!(ctlz(1i16), 15)]; + [assert_eq!(ctlz(1u32), 31), assert_eq!(ctlz(1i32), 31)]; + [assert_eq!(ctlz(1u64), 63), assert_eq!(ctlz(1i64), 63)]; + + [assert_eq!(ctlz(10u8), 4), assert_eq!(ctlz(10i8), 4)]; + [assert_eq!(ctlz(10u16), 12), assert_eq!(ctlz(10i16), 12)]; + [assert_eq!(ctlz(10u32), 28), assert_eq!(ctlz(10i32), 28)]; + [assert_eq!(ctlz(10u64), 60), assert_eq!(ctlz(10i64), 60)]; + + [assert_eq!(ctlz(100u8), 1), assert_eq!(ctlz(100i8), 1)]; + [assert_eq!(ctlz(100u16), 9), assert_eq!(ctlz(100i16), 9)]; + [assert_eq!(ctlz(100u32), 25), assert_eq!(ctlz(100i32), 25)]; + [assert_eq!(ctlz(100u64), 57), assert_eq!(ctlz(100i64), 57)]; + + [assert_eq!(ctlz_nonzero(1u8), 7), assert_eq!(ctlz_nonzero(1i8), 7)]; + [assert_eq!(ctlz_nonzero(1u16), 15), assert_eq!(ctlz_nonzero(1i16), 15)]; + [assert_eq!(ctlz_nonzero(1u32), 31), assert_eq!(ctlz_nonzero(1i32), 31)]; + [assert_eq!(ctlz_nonzero(1u64), 63), assert_eq!(ctlz_nonzero(1i64), 63)]; + + [assert_eq!(ctlz_nonzero(10u8), 4), assert_eq!(ctlz_nonzero(10i8), 4)]; + [assert_eq!(ctlz_nonzero(10u16), 12), assert_eq!(ctlz_nonzero(10i16), 12)]; + [assert_eq!(ctlz_nonzero(10u32), 28), assert_eq!(ctlz_nonzero(10i32), 28)]; + [assert_eq!(ctlz_nonzero(10u64), 60), assert_eq!(ctlz_nonzero(10i64), 60)]; + + [assert_eq!(ctlz_nonzero(100u8), 1), assert_eq!(ctlz_nonzero(100i8), 1)]; + [assert_eq!(ctlz_nonzero(100u16), 9), assert_eq!(ctlz_nonzero(100i16), 9)]; + [assert_eq!(ctlz_nonzero(100u32), 25), assert_eq!(ctlz_nonzero(100i32), 25)]; + [assert_eq!(ctlz_nonzero(100u64), 57), assert_eq!(ctlz_nonzero(100i64), 57)]; + + [assert_eq!(cttz(-1i8 as u8), 0), assert_eq!(cttz(-1i8), 0)]; + [assert_eq!(cttz(-1i16 as u16), 0), assert_eq!(cttz(-1i16), 0)]; + [assert_eq!(cttz(-1i32 as u32), 0), assert_eq!(cttz(-1i32), 0)]; + [assert_eq!(cttz(-1i64 as u64), 0), assert_eq!(cttz(-1i64), 0)]; + + [assert_eq!(cttz(0u8), 8), assert_eq!(cttz(0i8), 8)]; + [assert_eq!(cttz(0u16), 16), assert_eq!(cttz(0i16), 16)]; + [assert_eq!(cttz(0u32), 32), assert_eq!(cttz(0i32), 32)]; + [assert_eq!(cttz(0u64), 64), assert_eq!(cttz(0i64), 64)]; + + [assert_eq!(cttz(1u8), 0), assert_eq!(cttz(1i8), 0)]; + [assert_eq!(cttz(1u16), 0), assert_eq!(cttz(1i16), 0)]; + [assert_eq!(cttz(1u32), 0), assert_eq!(cttz(1i32), 0)]; + [assert_eq!(cttz(1u64), 0), assert_eq!(cttz(1i64), 0)]; + + [assert_eq!(cttz(10u8), 1), assert_eq!(cttz(10i8), 1)]; + [assert_eq!(cttz(10u16), 1), assert_eq!(cttz(10i16), 1)]; + [assert_eq!(cttz(10u32), 1), assert_eq!(cttz(10i32), 1)]; + [assert_eq!(cttz(10u64), 1), assert_eq!(cttz(10i64), 1)]; + + [assert_eq!(cttz(100u8), 2), assert_eq!(cttz(100i8), 2)]; + [assert_eq!(cttz(100u16), 2), assert_eq!(cttz(100i16), 2)]; + [assert_eq!(cttz(100u32), 2), assert_eq!(cttz(100i32), 2)]; + [assert_eq!(cttz(100u64), 2), assert_eq!(cttz(100i64), 2)]; + + [assert_eq!(cttz_nonzero(-1i8 as u8), 0), assert_eq!(cttz_nonzero(-1i8), 0)]; + [assert_eq!(cttz_nonzero(-1i16 as u16), 0), assert_eq!(cttz_nonzero(-1i16), 0)]; + [assert_eq!(cttz_nonzero(-1i32 as u32), 0), assert_eq!(cttz_nonzero(-1i32), 0)]; + [assert_eq!(cttz_nonzero(-1i64 as u64), 0), assert_eq!(cttz_nonzero(-1i64), 0)]; + + [assert_eq!(cttz_nonzero(1u8), 0), assert_eq!(cttz_nonzero(1i8), 0)]; + [assert_eq!(cttz_nonzero(1u16), 0), assert_eq!(cttz_nonzero(1i16), 0)]; + [assert_eq!(cttz_nonzero(1u32), 0), assert_eq!(cttz_nonzero(1i32), 0)]; + [assert_eq!(cttz_nonzero(1u64), 0), assert_eq!(cttz_nonzero(1i64), 0)]; + + [assert_eq!(cttz_nonzero(10u8), 1), assert_eq!(cttz_nonzero(10i8), 1)]; + [assert_eq!(cttz_nonzero(10u16), 1), assert_eq!(cttz_nonzero(10i16), 1)]; + [assert_eq!(cttz_nonzero(10u32), 1), assert_eq!(cttz_nonzero(10i32), 1)]; + [assert_eq!(cttz_nonzero(10u64), 1), assert_eq!(cttz_nonzero(10i64), 1)]; + + [assert_eq!(cttz_nonzero(100u8), 2), assert_eq!(cttz_nonzero(100i8), 2)]; + [assert_eq!(cttz_nonzero(100u16), 2), assert_eq!(cttz_nonzero(100i16), 2)]; + [assert_eq!(cttz_nonzero(100u32), 2), assert_eq!(cttz_nonzero(100i32), 2)]; + [assert_eq!(cttz_nonzero(100u64), 2), assert_eq!(cttz_nonzero(100i64), 2)]; + + assert_eq!(bswap(0x0Au8), 0x0A); // no-op + assert_eq!(bswap(0x0Ai8), 0x0A); // no-op + assert_eq!(bswap(0x0A0Bu16), 0x0B0A); + assert_eq!(bswap(0x0A0Bi16), 0x0B0A); + assert_eq!(bswap(0x0ABBCC0Du32), 0x0DCCBB0A); + assert_eq!(bswap(0x0ABBCC0Di32), 0x0DCCBB0A); + assert_eq!(bswap(0x0122334455667708u64), 0x0877665544332201); + assert_eq!(bswap(0x0122334455667708i64), 0x0877665544332201); + + assert_eq!(exact_div(9 * 9u32, 3), 27); + assert_eq!(exact_div(-9 * 9i32, 3), -27); + assert_eq!(exact_div(9 * 9i8, -3), -27); + assert_eq!(exact_div(-9 * 9i64, -3), 27); + + assert_eq!(unchecked_div(9 * 9u32, 2), 40); + assert_eq!(unchecked_div(-9 * 9i32, 2), -40); + assert_eq!(unchecked_div(9 * 9i8, -2), -40); + assert_eq!(unchecked_div(-9 * 9i64, -2), 40); + + assert_eq!(unchecked_rem(9 * 9u32, 2), 1); + assert_eq!(unchecked_rem(-9 * 9i32, 2), -1); + assert_eq!(unchecked_rem(9 * 9i8, -2), 1); + assert_eq!(unchecked_rem(-9 * 9i64, -2), -1); + + assert_eq!(unchecked_add(23u8, 19), 42); + assert_eq!(unchecked_add(5, -10), -5); + + assert_eq!(unchecked_sub(23u8, 19), 4); + assert_eq!(unchecked_sub(-17, -27), 10); + + assert_eq!(unchecked_mul(6u8, 7), 42); + assert_eq!(unchecked_mul(13, -5), -65); + } +} diff --git a/src/tools/miri/tests/pass/intrinsics-math.rs b/src/tools/miri/tests/pass/intrinsics-math.rs new file mode 100644 index 0000000000000..5973f4cd197fe --- /dev/null +++ b/src/tools/miri/tests/pass/intrinsics-math.rs @@ -0,0 +1,140 @@ +// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +macro_rules! assert_approx_eq { + ($a:expr, $b:expr) => {{ + let (a, b) = (&$a, &$b); + assert!((*a - *b).abs() < 1.0e-6, "{} is not approximately equal to {}", *a, *b); + }}; +} + +fn ldexp(a: f64, b: i32) -> f64 { + extern "C" { + fn ldexp(x: f64, n: i32) -> f64; + } + unsafe { ldexp(a, b) } +} + +pub fn main() { + use std::f32; + use std::f64; + + assert_approx_eq!(64f32.sqrt(), 8f32); + assert_approx_eq!(64f64.sqrt(), 8f64); + + assert_approx_eq!(25f32.powi(-2), 0.0016f32); + assert_approx_eq!(23.2f64.powi(2), 538.24f64); + + assert_approx_eq!(25f32.powf(-2f32), 0.0016f32); + assert_approx_eq!(400f64.powf(0.5f64), 20f64); + + assert_approx_eq!(1f32.exp(), f32::consts::E); + assert_approx_eq!(1f64.exp(), f64::consts::E); + + assert_approx_eq!(1f32.exp_m1(), f32::consts::E - 1.0); + assert_approx_eq!(1f64.exp_m1(), f64::consts::E - 1.0); + + assert_approx_eq!(10f32.exp2(), 1024f32); + assert_approx_eq!(50f64.exp2(), 1125899906842624f64); + + assert_approx_eq!(f32::consts::E.ln(), 1f32); + assert_approx_eq!(1f64.ln(), 0f64); + + assert_approx_eq!(0f32.ln_1p(), 0f32); + assert_approx_eq!(0f64.ln_1p(), 0f64); + + assert_approx_eq!(10f32.log10(), 1f32); + assert_approx_eq!(f64::consts::E.log10(), f64::consts::LOG10_E); + + assert_approx_eq!(8f32.log2(), 3f32); + assert_approx_eq!(f64::consts::E.log2(), f64::consts::LOG2_E); + + assert_approx_eq!(3.0f32.mul_add(2.0f32, 5.0f32), 11.0); + assert_eq!(0.0f32.mul_add(-2.0, f32::consts::E), f32::consts::E); + assert_approx_eq!(3.0f64.mul_add(2.0, 5.0), 11.0); + assert_eq!(0.0f64.mul_add(-2.0f64, f64::consts::E), f64::consts::E); + assert_eq!((-3.2f32).mul_add(2.4, f32::NEG_INFINITY), f32::NEG_INFINITY); + assert_eq!((-3.2f64).mul_add(2.4, f64::NEG_INFINITY), f64::NEG_INFINITY); + + assert_approx_eq!((-1.0f32).abs(), 1.0f32); + assert_approx_eq!(34.2f64.abs(), 34.2f64); + + #[allow(deprecated)] + { + assert_approx_eq!(5.0f32.abs_sub(3.0), 2.0); + assert_approx_eq!(3.0f64.abs_sub(5.0), 0.0); + } + + assert_approx_eq!(3.8f32.floor(), 3.0f32); + assert_approx_eq!((-1.1f64).floor(), -2.0f64); + + assert_approx_eq!((-2.3f32).ceil(), -2.0f32); + assert_approx_eq!(3.8f64.ceil(), 4.0f64); + + assert_approx_eq!(0.1f32.trunc(), 0.0f32); + assert_approx_eq!((-0.1f64).trunc(), 0.0f64); + + assert_approx_eq!(27.0f32.cbrt(), 3.0f32); + assert_approx_eq!(27.0f64.cbrt(), 3.0f64); + + assert_approx_eq!(3.0f32.hypot(4.0f32), 5.0f32); + assert_approx_eq!(3.0f64.hypot(4.0f64), 5.0f64); + + assert_eq!(3.3_f32.round(), 3.0); + assert_eq!(3.3_f64.round(), 3.0); + + assert_eq!(ldexp(0.65f64, 3i32), 5.2f64); + assert_eq!(ldexp(1.42, 0xFFFF), f64::INFINITY); + assert_eq!(ldexp(1.42, -0xFFFF), 0f64); + + // Trigonometric functions. + + assert_approx_eq!(0f32.sin(), 0f32); + assert_approx_eq!((f64::consts::PI / 2f64).sin(), 1f64); + assert_approx_eq!(f32::consts::FRAC_PI_6.sin(), 0.5); + assert_approx_eq!(f64::consts::FRAC_PI_6.sin(), 0.5); + assert_approx_eq!(f32::consts::FRAC_PI_4.sin().asin(), f32::consts::FRAC_PI_4); + assert_approx_eq!(f64::consts::FRAC_PI_4.sin().asin(), f64::consts::FRAC_PI_4); + + assert_approx_eq!(1.0f32.sinh(), 1.1752012f32); + assert_approx_eq!(1.0f64.sinh(), 1.1752012f64); + assert_approx_eq!(2.0f32.asinh(), 1.443635475178810342493276740273105f32); + assert_approx_eq!((-2.0f64).asinh(), -1.443635475178810342493276740273105f64); + + assert_approx_eq!(0f32.cos(), 1f32); + assert_approx_eq!((f64::consts::PI * 2f64).cos(), 1f64); + assert_approx_eq!(f32::consts::FRAC_PI_3.cos(), 0.5); + assert_approx_eq!(f64::consts::FRAC_PI_3.cos(), 0.5); + assert_approx_eq!(f32::consts::FRAC_PI_4.cos().acos(), f32::consts::FRAC_PI_4); + assert_approx_eq!(f64::consts::FRAC_PI_4.cos().acos(), f64::consts::FRAC_PI_4); + + assert_approx_eq!(1.0f32.cosh(), 1.54308f32); + assert_approx_eq!(1.0f64.cosh(), 1.54308f64); + assert_approx_eq!(2.0f32.acosh(), 1.31695789692481670862504634730796844f32); + assert_approx_eq!(3.0f64.acosh(), 1.76274717403908605046521864995958461f64); + + assert_approx_eq!(1.0f32.tan(), 1.557408f32); + assert_approx_eq!(1.0f64.tan(), 1.557408f64); + assert_approx_eq!(1.0_f32, 1.0_f32.tan().atan()); + assert_approx_eq!(1.0_f64, 1.0_f64.tan().atan()); + assert_approx_eq!(1.0f32.atan2(2.0f32), 0.46364761f32); + assert_approx_eq!(1.0f32.atan2(2.0f32), 0.46364761f32); + + assert_approx_eq!( + 1.0f32.tanh(), + (1.0 - f32::consts::E.powi(-2)) / (1.0 + f32::consts::E.powi(-2)) + ); + assert_approx_eq!( + 1.0f64.tanh(), + (1.0 - f64::consts::E.powi(-2)) / (1.0 + f64::consts::E.powi(-2)) + ); + assert_approx_eq!(0.5f32.atanh(), 0.54930614433405484569762261846126285f32); + assert_approx_eq!(0.5f64.atanh(), 0.54930614433405484569762261846126285f64); +} diff --git a/src/tools/miri/tests/pass/intrinsics-x86.rs b/src/tools/miri/tests/pass/intrinsics-x86.rs new file mode 100644 index 0000000000000..88cd782e70a46 --- /dev/null +++ b/src/tools/miri/tests/pass/intrinsics-x86.rs @@ -0,0 +1,22 @@ +#[cfg(target_arch = "x86_64")] +mod x86_64 { + use core::arch::x86_64 as arch; + + fn adc(c_in: u8, a: u64, b: u64) -> (u8, u64) { + let mut sum = 0; + // SAFETY: There are no safety requirements for calling `_addcarry_u64`. + // It's just unsafe for API consistency with other intrinsics. + let c_out = unsafe { arch::_addcarry_u64(c_in, a, b, &mut sum) }; + (c_out, sum) + } + + pub fn main() { + assert_eq!(adc(1, 1, 1), (0, 3)); + assert_eq!(adc(3, u64::MAX, u64::MAX), (2, 1)); + } +} + +fn main() { + #[cfg(target_arch = "x86_64")] + x86_64::main(); +} diff --git a/src/tools/miri/tests/pass/intrinsics.rs b/src/tools/miri/tests/pass/intrinsics.rs new file mode 100644 index 0000000000000..6267e6e6bc111 --- /dev/null +++ b/src/tools/miri/tests/pass/intrinsics.rs @@ -0,0 +1,42 @@ +//@compile-flags: -Zmiri-permissive-provenance +#![feature(core_intrinsics, layout_for_ptr)] +//! Tests for various intrinsics that do not fit anywhere else. + +use std::intrinsics; +use std::mem::{size_of, size_of_val, size_of_val_raw}; + +struct Bomb; + +impl Drop for Bomb { + fn drop(&mut self) { + eprintln!("BOOM!"); + } +} + +fn main() { + assert_eq!(size_of::>(), 8); + assert_eq!(size_of_val(&()), 0); + assert_eq!(size_of_val(&42), 4); + assert_eq!(size_of_val(&[] as &[i32]), 0); + assert_eq!(size_of_val(&[1, 2, 3] as &[i32]), 12); + assert_eq!(size_of_val("foobar"), 6); + + unsafe { + assert_eq!(size_of_val_raw(&[1] as &[i32] as *const [i32]), 4); + } + unsafe { + assert_eq!(size_of_val_raw(0x100 as *const i32), 4); + } + + assert_eq!(intrinsics::type_name::>(), "core::option::Option"); + + assert_eq!(intrinsics::likely(false), false); + assert_eq!(intrinsics::unlikely(true), true); + + intrinsics::forget(Bomb); + + let _v = intrinsics::discriminant_value(&Some(())); + let _v = intrinsics::discriminant_value(&0); + let _v = intrinsics::discriminant_value(&true); + let _v = intrinsics::discriminant_value(&vec![1, 2, 3]); +} diff --git a/src/tools/miri/tests/pass/ints.rs b/src/tools/miri/tests/pass/ints.rs new file mode 100644 index 0000000000000..c04c6921f3c42 --- /dev/null +++ b/src/tools/miri/tests/pass/ints.rs @@ -0,0 +1,58 @@ +fn ret() -> i64 { + 1 +} + +fn neg() -> i64 { + -1 +} + +fn add() -> i64 { + 1 + 2 +} + +fn indirect_add() -> i64 { + let x = 1; + let y = 2; + x + y +} + +fn arith() -> i32 { + 3 * 3 + 4 * 4 +} + +fn match_int() -> i16 { + let n = 2; + match n { + 0 => 0, + 1 => 10, + 2 => 20, + 3 => 30, + _ => 100, + } +} + +fn match_int_range() -> i64 { + let n = 42; + match n { + 0..=9 => 0, + 10..=19 => 1, + 20..=29 => 2, + 30..=39 => 3, + 40..=42 => 4, + _ => 5, + } +} + +fn main() { + assert_eq!(ret(), 1); + assert_eq!(neg(), -1); + assert_eq!(add(), 3); + assert_eq!(indirect_add(), 3); + assert_eq!(arith(), 5 * 5); + assert_eq!(match_int(), 20); + assert_eq!(match_int_range(), 4); + assert_eq!(i64::MIN.overflowing_mul(-1), (i64::MIN, true)); + assert_eq!(i32::MIN.overflowing_mul(-1), (i32::MIN, true)); + assert_eq!(i16::MIN.overflowing_mul(-1), (i16::MIN, true)); + assert_eq!(i8::MIN.overflowing_mul(-1), (i8::MIN, true)); +} diff --git a/src/tools/miri/tests/pass/issues/issue-15063.rs b/src/tools/miri/tests/pass/issues/issue-15063.rs new file mode 100644 index 0000000000000..cbb1b90f68629 --- /dev/null +++ b/src/tools/miri/tests/pass/issues/issue-15063.rs @@ -0,0 +1,11 @@ +#[allow(dead_code)] +enum Two { + A, + B, +} +impl Drop for Two { + fn drop(&mut self) {} +} +fn main() { + let _k = Two::A; +} diff --git a/src/tools/miri/tests/pass/issues/issue-15080.rs b/src/tools/miri/tests/pass/issues/issue-15080.rs new file mode 100644 index 0000000000000..4a360993116c3 --- /dev/null +++ b/src/tools/miri/tests/pass/issues/issue-15080.rs @@ -0,0 +1,19 @@ +fn main() { + let mut x: &[_] = &[1, 2, 3, 4]; + + let mut result = vec![]; + loop { + x = match *x { + [1, n, 3, ref rest @ ..] => { + result.push(n); + rest + } + [n, ref rest @ ..] => { + result.push(n); + rest + } + [] => break, + } + } + assert_eq!(result, [2, 4]); +} diff --git a/src/tools/miri/tests/pass/issues/issue-15523-big.rs b/src/tools/miri/tests/pass/issues/issue-15523-big.rs new file mode 100644 index 0000000000000..7c9fec3ab04b0 --- /dev/null +++ b/src/tools/miri/tests/pass/issues/issue-15523-big.rs @@ -0,0 +1,37 @@ +// Issue 15523: derive(PartialOrd) should use the provided +// discriminant values for the derived ordering. +// +// This test is checking corner cases that arise when you have +// 64-bit values in the variants. + +#[derive(PartialEq, PartialOrd)] +#[repr(u64)] +enum Eu64 { + Pos2 = 2, + PosMax = !0, + Pos1 = 1, +} + +#[derive(PartialEq, PartialOrd)] +#[repr(i64)] +enum Ei64 { + Pos2 = 2, + Neg1 = -1, + NegMin = 1 << 63, + PosMax = !(1 << 63), + Pos1 = 1, +} + +fn main() { + assert!(Eu64::Pos2 > Eu64::Pos1); + assert!(Eu64::Pos2 < Eu64::PosMax); + assert!(Eu64::Pos1 < Eu64::PosMax); + + assert!(Ei64::Pos2 > Ei64::Pos1); + assert!(Ei64::Pos2 > Ei64::Neg1); + assert!(Ei64::Pos1 > Ei64::Neg1); + assert!(Ei64::Pos2 > Ei64::NegMin); + assert!(Ei64::Pos1 > Ei64::NegMin); + assert!(Ei64::Pos2 < Ei64::PosMax); + assert!(Ei64::Pos1 < Ei64::PosMax); +} diff --git a/src/tools/miri/tests/pass/issues/issue-17877.rs b/src/tools/miri/tests/pass/issues/issue-17877.rs new file mode 100644 index 0000000000000..64d397f91aeec --- /dev/null +++ b/src/tools/miri/tests/pass/issues/issue-17877.rs @@ -0,0 +1,17 @@ +fn main() { + assert_eq!( + match [0u8; 16 * 1024] { + _ => 42_usize, + }, + 42_usize, + ); + + assert_eq!( + match [0u8; 16 * 1024] { + [1, ..] => 0_usize, + [0, ..] => 1_usize, + _ => 2_usize, + }, + 1_usize, + ); +} diff --git a/src/tools/miri/tests/pass/issues/issue-20575.rs b/src/tools/miri/tests/pass/issues/issue-20575.rs new file mode 100644 index 0000000000000..19049b9add5c1 --- /dev/null +++ b/src/tools/miri/tests/pass/issues/issue-20575.rs @@ -0,0 +1,7 @@ +// Test that overloaded calls work with zero arity closures + +fn main() { + let functions: [Box Option<()>>; 1] = [Box::new(|| None)]; + + let _val: Option> = functions.iter().map(|f| (*f)()).collect(); +} diff --git a/src/tools/miri/tests/pass/issues/issue-23261.rs b/src/tools/miri/tests/pass/issues/issue-23261.rs new file mode 100644 index 0000000000000..f98252c18bf5c --- /dev/null +++ b/src/tools/miri/tests/pass/issues/issue-23261.rs @@ -0,0 +1,60 @@ +// Matching on a DST struct should not trigger an LLVM assertion. + +struct Foo { + a: i32, + inner: T, +} + +trait Get { + fn get(&self) -> i32; +} + +impl Get for i32 { + fn get(&self) -> i32 { + *self + } +} + +fn check_val(val: &Foo<[u8]>) { + match *val { + Foo { a, .. } => { + assert_eq!(a, 32); + } + } +} + +fn check_dst_val(val: &Foo<[u8]>) { + match *val { + Foo { ref inner, .. } => { + assert_eq!(inner, [1, 2, 3]); + } + } +} + +fn check_both(val: &Foo<[u8]>) { + match *val { + Foo { a, ref inner } => { + assert_eq!(a, 32); + assert_eq!(inner, [1, 2, 3]); + } + } +} + +fn check_trait_obj(val: &Foo) { + match *val { + Foo { a, ref inner } => { + assert_eq!(a, 32); + assert_eq!(inner.get(), 32); + } + } +} + +fn main() { + let foo: &Foo<[u8]> = &Foo { a: 32, inner: [1, 2, 3] }; + check_val(foo); + check_dst_val(foo); + check_both(foo); + + let foo: &Foo = &Foo { a: 32, inner: 32 }; + check_trait_obj(foo); +} diff --git a/src/tools/miri/tests/pass/issues/issue-26709.rs b/src/tools/miri/tests/pass/issues/issue-26709.rs new file mode 100644 index 0000000000000..78f30e78db766 --- /dev/null +++ b/src/tools/miri/tests/pass/issues/issue-26709.rs @@ -0,0 +1,16 @@ +struct Wrapper<'a, T: ?Sized>(&'a mut i32, T); + +impl<'a, T: ?Sized> Drop for Wrapper<'a, T> { + fn drop(&mut self) { + *self.0 = 432; + } +} + +fn main() { + let mut x = 0; + { + let wrapper = Box::new(Wrapper(&mut x, 123)); + let _val: Box> = wrapper; + } + assert_eq!(432, x) +} diff --git a/src/tools/miri/tests/pass/issues/issue-27901.rs b/src/tools/miri/tests/pass/issues/issue-27901.rs new file mode 100644 index 0000000000000..a9f5dc98720fc --- /dev/null +++ b/src/tools/miri/tests/pass/issues/issue-27901.rs @@ -0,0 +1,14 @@ +trait Stream { + type Item; +} +impl<'a> Stream for &'a str { + type Item = u8; +} +fn f<'s>(s: &'s str) -> (&'s str, <&'s str as Stream>::Item) { + (s, 42) +} + +fn main() { + let fx = f as for<'t> fn(&'t str) -> (&'t str, <&'t str as Stream>::Item); + assert_eq!(fx("hi"), ("hi", 42)); +} diff --git a/src/tools/miri/tests/pass/issues/issue-29746.rs b/src/tools/miri/tests/pass/issues/issue-29746.rs new file mode 100644 index 0000000000000..43bed4464b9c8 --- /dev/null +++ b/src/tools/miri/tests/pass/issues/issue-29746.rs @@ -0,0 +1,35 @@ +// zip!(a1,a2,a3,a4) is equivalent to: +// a1.zip(a2).zip(a3).zip(a4).map(|(((x1,x2),x3),x4)| (x1,x2,x3,x4)) +macro_rules! zip { + // Entry point + ([$a:expr, $b:expr, $($rest:expr),*]) => { + zip!([$($rest),*], $a.zip($b), (x,y), [x,y]) + }; + + // Intermediate steps to build the zipped expression, the match pattern, and + // and the output tuple of the closure, using macro hygene to repeatedly + // introduce new variables named 'x'. + ([$a:expr, $($rest:expr),*], $zip:expr, $pat:pat, [$($flat:expr),*]) => { + zip!([$($rest),*], $zip.zip($a), ($pat,x), [$($flat),*, x]) + }; + + // Final step + ([], $zip:expr, $pat:pat, [$($flat:expr),+]) => { + $zip.map(|$pat| ($($flat),+)) + }; + + // Comma + ([$a:expr], $zip:expr, $pat:pat, [$($flat:expr),*]) => { + zip!([$a,], $zip, $pat, [$($flat),*]) + }; +} + +fn main() { + let p1 = vec![1i32, 2].into_iter(); + let p2 = vec!["10", "20"].into_iter(); + let p3 = vec![100u16, 200].into_iter(); + let p4 = vec![1000i64, 2000].into_iter(); + + let e = zip!([p1, p2, p3, p4]).collect::>(); + assert_eq!(e[0], (1i32, "10", 100u16, 1000i64)); +} diff --git a/src/tools/miri/tests/pass/issues/issue-30530.rs b/src/tools/miri/tests/pass/issues/issue-30530.rs new file mode 100644 index 0000000000000..472b42adaac85 --- /dev/null +++ b/src/tools/miri/tests/pass/issues/issue-30530.rs @@ -0,0 +1,38 @@ +// Copyright 2012-2016 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Regression test for Issue #30530: alloca's created for storing +// intermediate scratch values during brace-less match arms need to be +// initialized with their drop-flag set to "dropped" (or else we end +// up running the destructors on garbage data at the end of the +// function). + +pub enum Handler { + Default, + #[allow(dead_code)] + Custom(*mut Box), +} + +fn main() { + #[allow(unused_must_use)] + { + take(Handler::Default, Box::new(main)); + } +} + +#[inline(never)] +pub fn take(h: Handler, f: Box) -> Box { + unsafe { + match h { + Handler::Custom(ptr) => *Box::from_raw(ptr), + Handler::Default => f, + } + } +} diff --git a/src/tools/miri/tests/pass/issues/issue-31267-additional.rs b/src/tools/miri/tests/pass/issues/issue-31267-additional.rs new file mode 100644 index 0000000000000..f6d7209369b70 --- /dev/null +++ b/src/tools/miri/tests/pass/issues/issue-31267-additional.rs @@ -0,0 +1,17 @@ +#[derive(Clone, Copy, Debug)] +struct Bar; + +const BAZ: Bar = Bar; + +#[derive(Debug)] +struct Foo([Bar; 1]); + +struct Biz; + +impl Biz { + const BAZ: Foo = Foo([BAZ; 1]); +} + +fn main() { + let _foo = Biz::BAZ; +} diff --git a/src/tools/miri/tests/pass/issues/issue-33387.rs b/src/tools/miri/tests/pass/issues/issue-33387.rs new file mode 100644 index 0000000000000..36b58c642d7d5 --- /dev/null +++ b/src/tools/miri/tests/pass/issues/issue-33387.rs @@ -0,0 +1,9 @@ +use std::sync::Arc; + +trait Foo {} + +impl Foo for [u8; 2] {} + +fn main() { + let _val: Arc = Arc::new([3, 4]); +} diff --git a/src/tools/miri/tests/pass/issues/issue-34571.rs b/src/tools/miri/tests/pass/issues/issue-34571.rs new file mode 100644 index 0000000000000..e1ed8d19e4ea6 --- /dev/null +++ b/src/tools/miri/tests/pass/issues/issue-34571.rs @@ -0,0 +1,10 @@ +#[repr(u8)] +enum Foo { + Foo(u8), +} + +fn main() { + match Foo::Foo(1) { + _ => (), + } +} diff --git a/src/tools/miri/tests/pass/issues/issue-35815.rs b/src/tools/miri/tests/pass/issues/issue-35815.rs new file mode 100644 index 0000000000000..62b3220967edd --- /dev/null +++ b/src/tools/miri/tests/pass/issues/issue-35815.rs @@ -0,0 +1,14 @@ +use std::mem; + +#[allow(dead_code)] +struct Foo { + a: i64, + b: bool, + c: T, +} + +fn main() { + let foo: &Foo = &Foo { a: 1, b: false, c: 2i32 }; + let foo_unsized: &Foo = foo; + assert_eq!(mem::size_of_val(foo), mem::size_of_val(foo_unsized)); +} diff --git a/src/tools/miri/tests/pass/issues/issue-36278-prefix-nesting.rs b/src/tools/miri/tests/pass/issues/issue-36278-prefix-nesting.rs new file mode 100644 index 0000000000000..6bc8f02c3bafb --- /dev/null +++ b/src/tools/miri/tests/pass/issues/issue-36278-prefix-nesting.rs @@ -0,0 +1,22 @@ +// Issue 36278: On an unsized struct with >1 level of nontrivial +// nesting, ensure we are computing dynamic size of prefix correctly. + +use std::mem; + +const SZ: usize = 100; +struct P([u8; SZ], T); + +type Ack = P>; + +fn main() { + let size_of_sized; + let size_of_unsized; + let x: Box> = Box::new(P([0; SZ], P([0; SZ], [0; 0]))); + size_of_sized = mem::size_of_val::>(&x); + let align_of_sized = mem::align_of_val::>(&x); + let y: Box> = x; + size_of_unsized = mem::size_of_val::>(&y); + assert_eq!(size_of_sized, size_of_unsized); + assert_eq!(align_of_sized, 1); + assert_eq!(mem::align_of_val::>(&y), 1); +} diff --git a/src/tools/miri/tests/pass/issues/issue-3794.rs b/src/tools/miri/tests/pass/issues/issue-3794.rs new file mode 100644 index 0000000000000..5b5b22b54948d --- /dev/null +++ b/src/tools/miri/tests/pass/issues/issue-3794.rs @@ -0,0 +1,32 @@ +#![feature(box_syntax)] + +trait T { + fn print(&self); +} + +#[derive(Debug)] +struct S { + #[allow(dead_code)] + s: isize, +} + +impl T for S { + fn print(&self) { + println!("{:?}", self); + } +} + +fn print_t(t: &dyn T) { + t.print(); +} + +fn print_s(s: &S) { + s.print(); +} + +pub fn main() { + let s: Box = box S { s: 5 }; + print_s(&*s); + let t: Box = s as Box; + print_t(&*t); +} diff --git a/src/tools/miri/tests/pass/issues/issue-3794.stdout b/src/tools/miri/tests/pass/issues/issue-3794.stdout new file mode 100644 index 0000000000000..e4afe6fa55f15 --- /dev/null +++ b/src/tools/miri/tests/pass/issues/issue-3794.stdout @@ -0,0 +1,2 @@ +S { s: 5 } +S { s: 5 } diff --git a/src/tools/miri/tests/pass/issues/issue-53728.rs b/src/tools/miri/tests/pass/issues/issue-53728.rs new file mode 100644 index 0000000000000..0c858d3444fb3 --- /dev/null +++ b/src/tools/miri/tests/pass/issues/issue-53728.rs @@ -0,0 +1,18 @@ +#[repr(u16)] +#[allow(dead_code)] +enum DeviceKind { + Nil = 0, +} + +#[repr(packed)] +#[allow(dead_code)] +struct DeviceInfo { + endianness: u8, + device_kind: DeviceKind, +} + +fn main() { + let _x = None::<(DeviceInfo, u8)>; + let _y = None::<(DeviceInfo, u16)>; + let _z = None::<(DeviceInfo, u64)>; +} diff --git a/src/tools/miri/tests/pass/issues/issue-5917.rs b/src/tools/miri/tests/pass/issues/issue-5917.rs new file mode 100644 index 0000000000000..f7bbb4350e2be --- /dev/null +++ b/src/tools/miri/tests/pass/issues/issue-5917.rs @@ -0,0 +1,6 @@ +struct T(&'static [isize]); +static STATIC: T = T(&[5, 4, 3]); +pub fn main() { + let T(ref v) = STATIC; + assert_eq!(v[0], 5); +} diff --git a/src/tools/miri/tests/pass/issues/issue-73223.rs b/src/tools/miri/tests/pass/issues/issue-73223.rs new file mode 100644 index 0000000000000..df13787aad698 --- /dev/null +++ b/src/tools/miri/tests/pass/issues/issue-73223.rs @@ -0,0 +1,23 @@ +fn main() { + let mut state = State { prev: None, next: Some(8) }; + let path = "/nested/some/more"; + assert_eq!(state.rest(path), "some/more"); +} + +#[allow(unused)] +struct State { + prev: Option, + next: Option, +} + +impl State { + fn rest<'r>(&mut self, path: &'r str) -> &'r str { + let start = match self.next.take() { + Some(v) => v, + None => return "", + }; + + self.prev = Some(start); + &path[start..] + } +} diff --git a/src/tools/miri/tests/pass/issues/issue-91636.rs b/src/tools/miri/tests/pass/issues/issue-91636.rs new file mode 100644 index 0000000000000..21000bb68d2bc --- /dev/null +++ b/src/tools/miri/tests/pass/issues/issue-91636.rs @@ -0,0 +1,20 @@ +type BuiltIn = for<'a> fn(&str); + +struct Function { + inner: BuiltIn, +} + +impl Function { + fn new(subr: BuiltIn) -> Self { + Self { inner: subr } + } +} + +fn dummy(_: &str) {} + +fn main() { + let func1 = Function::new(dummy); + let func2 = Function::new(dummy); + let inner: fn(&'static _) -> _ = func1.inner; + assert!(inner == func2.inner); +} diff --git a/src/tools/miri/tests/pass/issues/issue-94371.rs b/src/tools/miri/tests/pass/issues/issue-94371.rs new file mode 100644 index 0000000000000..1336348efb17a --- /dev/null +++ b/src/tools/miri/tests/pass/issues/issue-94371.rs @@ -0,0 +1,13 @@ +#[repr(C)] +struct Demo(u64, bool, u64, u32, u64, u64, u64); + +fn test() -> (Demo, Demo) { + let mut x = Demo(1, true, 3, 4, 5, 6, 7); + let mut y = Demo(10, false, 12, 13, 14, 15, 16); + std::mem::swap(&mut x, &mut y); + (x, y) +} + +fn main() { + drop(test()); +} diff --git a/src/tools/miri/tests/pass/issues/issue-miri-1075.rs b/src/tools/miri/tests/pass/issues/issue-miri-1075.rs new file mode 100644 index 0000000000000..8bacaca9c2ac2 --- /dev/null +++ b/src/tools/miri/tests/pass/issues/issue-miri-1075.rs @@ -0,0 +1,6 @@ +fn main() { + let f: fn() -> ! = || std::process::exit(0); + f(); + + // FIXME: Also add a test for , once that is fixed. +} diff --git a/src/tools/miri/tests/pass/issues/issue-miri-133.rs b/src/tools/miri/tests/pass/issues/issue-miri-133.rs new file mode 100644 index 0000000000000..02c9732571330 --- /dev/null +++ b/src/tools/miri/tests/pass/issues/issue-miri-133.rs @@ -0,0 +1,25 @@ +use std::mem::size_of; + +struct S { + _u: U, + size_of_u: usize, + _v: V, + size_of_v: usize, +} + +impl S { + fn new(u: U, v: V) -> Self { + S { _u: u, size_of_u: size_of::(), _v: v, size_of_v: size_of::() } + } +} + +impl Drop for S { + fn drop(&mut self) { + assert_eq!(size_of::(), self.size_of_u); + assert_eq!(size_of::(), self.size_of_v); + } +} + +fn main() { + S::new(0u8, 1u16); +} diff --git a/src/tools/miri/tests/pass/issues/issue-miri-184.rs b/src/tools/miri/tests/pass/issues/issue-miri-184.rs new file mode 100644 index 0000000000000..39c841403ef0c --- /dev/null +++ b/src/tools/miri/tests/pass/issues/issue-miri-184.rs @@ -0,0 +1,4 @@ +pub fn main() { + let bytes: [u8; 8] = unsafe { ::std::mem::transmute(0u64) }; + let _val: &[u8] = &bytes; +} diff --git a/src/tools/miri/tests/pass/issues/issue-miri-1925.rs b/src/tools/miri/tests/pass/issues/issue-miri-1925.rs new file mode 100644 index 0000000000000..8655681349194 --- /dev/null +++ b/src/tools/miri/tests/pass/issues/issue-miri-1925.rs @@ -0,0 +1,33 @@ +//@compile-flags: -Zmiri-symbolic-alignment-check + +use std::mem::size_of; + +fn main() { + let mut a = Params::new(); + a.key_block = [0; BLOCKBYTES]; +} + +#[repr(C)] +#[derive(Clone)] +#[allow(unused)] +pub struct Params { + hash_length: u8, + key_length: u8, + key_block: [u8; BLOCKBYTES], + max_leaf_length: u32, +} + +pub const OUTBYTES: usize = 8 * size_of::(); +pub const KEYBYTES: usize = 8 * size_of::(); +pub const BLOCKBYTES: usize = 16 * size_of::(); + +impl Params { + pub fn new() -> Self { + Self { + hash_length: OUTBYTES as u8, + key_length: 0, + key_block: [0; BLOCKBYTES], + max_leaf_length: 0, + } + } +} diff --git a/src/tools/miri/tests/pass/issues/issue-miri-2068.rs b/src/tools/miri/tests/pass/issues/issue-miri-2068.rs new file mode 100644 index 0000000000000..7576ba78f607d --- /dev/null +++ b/src/tools/miri/tests/pass/issues/issue-miri-2068.rs @@ -0,0 +1,48 @@ +#![feature(pin_macro)] + +use core::future::Future; +use core::pin::Pin; +use core::task::{Context, Poll}; + +use std::sync::Arc; + +struct NopWaker; + +impl std::task::Wake for NopWaker { + fn wake(self: Arc) {} +} + +pub fn fuzzing_block_on>(fut: F) -> O { + let mut fut = std::pin::pin!(fut); + let waker = std::task::Waker::from(Arc::new(NopWaker)); + let mut context = std::task::Context::from_waker(&waker); + loop { + match fut.as_mut().poll(&mut context) { + Poll::Ready(v) => return v, + Poll::Pending => {} + } + } +} + +pub struct LastFuture { + last: S, +} + +impl Future for LastFuture +where + Self: Unpin, + S: Unpin + Copy, +{ + type Output = S; + + fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { + return Poll::Ready(self.last); + } +} + +fn main() { + fuzzing_block_on(async { + LastFuture { last: &0u32 }.await; + LastFuture { last: Option::::None }.await; + }); +} diff --git a/src/tools/miri/tests/pass/issues/issue-miri-2123.rs b/src/tools/miri/tests/pass/issues/issue-miri-2123.rs new file mode 100644 index 0000000000000..e39e5fe454a2c --- /dev/null +++ b/src/tools/miri/tests/pass/issues/issue-miri-2123.rs @@ -0,0 +1,21 @@ +#![feature(ptr_metadata, layout_for_ptr)] + +use std::{mem, ptr}; + +trait Foo {} + +impl Foo for u32 {} + +fn uwu(thin: *const (), meta: &'static ()) -> *const dyn Foo { + ptr::from_raw_parts(thin, unsafe { mem::transmute(meta) }) +} + +fn main() { + unsafe { + let orig = 1_u32; + let x = &orig as &dyn Foo; + let (ptr, meta) = (x as *const dyn Foo).to_raw_parts(); + let ptr = uwu(ptr, mem::transmute(meta)); + let _size = mem::size_of_val_raw(ptr); + } +} diff --git a/src/tools/miri/tests/pass/iter.rs b/src/tools/miri/tests/pass/iter.rs new file mode 100644 index 0000000000000..31d0d7442d9d2 --- /dev/null +++ b/src/tools/miri/tests/pass/iter.rs @@ -0,0 +1,41 @@ +fn iter_empty_and_zst() { + // Iterate over a Unique::empty() + for _ in Vec::::new().iter() { + panic!("We should never be here."); + } + + // Iterate over a ZST (uses arith_offset internally) + let mut count = 0; + for _ in &[(), (), ()] { + count += 1; + } + assert_eq!(count, 3); +} + +fn test_iterator_step_by_nth() { + let mut it = (0..16).step_by(5); + assert_eq!(it.nth(0), Some(0)); + assert_eq!(it.nth(0), Some(5)); + assert_eq!(it.nth(0), Some(10)); + assert_eq!(it.nth(0), Some(15)); + assert_eq!(it.nth(0), None); +} + +fn iter_any() { + let f = |x: &u8| 10u8 == *x; + f(&1u8); + + let g = |(), x: &u8| 10u8 == *x; + g((), &1u8); + + let h = |(), (), x: &u8| 10u8 == *x; + h((), (), &1u8); + + [1, 2, 3u8].iter().any(|elt| 10 == *elt); +} + +fn main() { + test_iterator_step_by_nth(); + iter_any(); + iter_empty_and_zst(); +} diff --git a/src/tools/miri/tests/pass/last-use-in-cap-clause.rs b/src/tools/miri/tests/pass/last-use-in-cap-clause.rs new file mode 100644 index 0000000000000..2160aea16346f --- /dev/null +++ b/src/tools/miri/tests/pass/last-use-in-cap-clause.rs @@ -0,0 +1,17 @@ +// Make sure #1399 stays fixed + +#[allow(dead_code)] +struct A { + a: Box, +} + +fn foo() -> Box isize + 'static> { + let k: Box<_> = Box::new(22); + let _u = A { a: k.clone() }; + let result = || 22; + Box::new(result) +} + +pub fn main() { + assert_eq!(foo()(), 22); +} diff --git a/src/tools/miri/tests/pass/leak-in-static.rs b/src/tools/miri/tests/pass/leak-in-static.rs new file mode 100644 index 0000000000000..9523394408806 --- /dev/null +++ b/src/tools/miri/tests/pass/leak-in-static.rs @@ -0,0 +1,23 @@ +use std::{ + ptr, + sync::atomic::{AtomicPtr, Ordering}, +}; + +static mut LEAKER: Option>> = None; + +fn main() { + // Having memory "leaked" in globals is allowed. + unsafe { + LEAKER = Some(Box::new(vec![0; 42])); + } + + // Make sure this is allowed even when `AtomicPtr` is used. + { + static LEAK: AtomicPtr = AtomicPtr::new(ptr::null_mut()); + LEAK.store(Box::into_raw(Box::new(0usize)), Ordering::SeqCst); + + static LEAK2: AtomicPtr = AtomicPtr::new(ptr::null_mut()); + // Make sure this also works when using 'swap'. + LEAK2.swap(Box::into_raw(Box::new(0usize)), Ordering::SeqCst); + } +} diff --git a/src/tools/miri/tests/pass/linked-list.rs b/src/tools/miri/tests/pass/linked-list.rs new file mode 100644 index 0000000000000..7377f9f60b01e --- /dev/null +++ b/src/tools/miri/tests/pass/linked-list.rs @@ -0,0 +1,51 @@ +#![feature(linked_list_cursors)] +use std::collections::LinkedList; + +fn list_from(v: &[T]) -> LinkedList { + v.iter().cloned().collect() +} + +// Gather all references from a mutable iterator and make sure Miri notices if +// using them is dangerous. +fn test_all_refs<'a, T: 'a>(dummy: &mut T, iter: impl Iterator) { + // Gather all those references. + let mut refs: Vec<&mut T> = iter.collect(); + // Use them all. Twice, to be sure we got all interleavings. + for r in refs.iter_mut() { + std::mem::swap(dummy, r); + } + for r in refs { + std::mem::swap(dummy, r); + } +} + +fn main() { + let mut m = list_from(&[0, 2, 4, 6, 8]); + let len = m.len(); + { + let mut it = m.cursor_front_mut(); + it.insert_before(-2); + loop { + match it.current().copied() { + None => break, + Some(elt) => { + match it.peek_next() { + Some(x) => assert_eq!(*x, elt + 2), + None => assert_eq!(8, elt), + } + it.insert_after(elt + 1); + it.move_next(); // Move by 2 to skip the one we inserted. + it.move_next(); + } + } + } + it.insert_before(99); + it.insert_after(-10); + } + + assert_eq!(m.len(), 3 + len * 2); + let mut m2 = m.clone(); + assert_eq!(m.into_iter().collect::>(), [-10, -2, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 99]); + + test_all_refs(&mut 13, m2.iter_mut()); +} diff --git a/src/tools/miri/tests/pass/loop-break-value.rs b/src/tools/miri/tests/pass/loop-break-value.rs new file mode 100644 index 0000000000000..bc4c967d26a5b --- /dev/null +++ b/src/tools/miri/tests/pass/loop-break-value.rs @@ -0,0 +1,125 @@ +#![feature(never_type)] +#![allow(unreachable_code)] + +#[allow(unused)] +fn never_returns() { + loop { + break loop {}; + } +} + +pub fn main() { + let value = 'outer: loop { + if 1 == 1 { + break 13; + } else { + let _never: ! = loop { + break loop { + break 'outer panic!(); + }; + }; + } + }; + assert_eq!(value, 13); + + let x = [1, 3u32, 5]; + let y = [17]; + let z = []; + let coerced: &[_] = loop { + match 2 { + 1 => break &x, + 2 => break &y, + 3 => break &z, + _ => (), + } + }; + assert_eq!(coerced, &[17u32]); + + let trait_unified = loop { + break if true { break Default::default() } else { break [13, 14] }; + }; + assert_eq!(trait_unified, [0, 0]); + + let trait_unified_2 = loop { + if false { + break [String::from("Hello")]; + } else { + break Default::default(); + }; + }; + // compare lengths; ptr comparison is not deterministic + assert_eq!(trait_unified_2.len(), 1); + assert_eq!(trait_unified_2[0].len(), 0); + + let trait_unified_3 = loop { + break if false { break [String::from("Hello")] } else { ["Yes".into()] }; + }; + assert_eq!(trait_unified_3, ["Yes"]); + + let regular_break = loop { + if true { + break; + } else { + break break Default::default(); + } + }; + assert_eq!(regular_break, ()); + + let regular_break_2 = loop { + if true { + break Default::default(); + } else { + break; + } + }; + assert_eq!(regular_break_2, ()); + + let regular_break_3 = loop { + break if true { + Default::default() + } else { + break; + }; + }; + assert_eq!(regular_break_3, ()); + + let regular_break_4 = loop { + break (); + break; + }; + assert_eq!(regular_break_4, ()); + + let regular_break_5 = loop { + break; + break (); + }; + assert_eq!(regular_break_5, ()); + + let nested_break_value = 'outer2: loop { + let _a: u32 = 'inner: loop { + if true { + break 'outer2 "hello"; + } else { + break 'inner 17; + } + }; + panic!(); + }; + assert_eq!(nested_break_value, "hello"); + + let break_from_while_cond = loop { + 'inner_loop: while break 'inner_loop { + panic!(); + } + break 123; + }; + assert_eq!(break_from_while_cond, 123); + + let break_from_while_to_outer = 'outer_loop: loop { + while break 'outer_loop 567 { + panic!("from_inner"); + } + panic!("from outer"); + }; + assert_eq!(break_from_while_to_outer, 567); +} diff --git a/src/tools/miri/tests/pass/loops.rs b/src/tools/miri/tests/pass/loops.rs new file mode 100644 index 0000000000000..222287cbe09ad --- /dev/null +++ b/src/tools/miri/tests/pass/loops.rs @@ -0,0 +1,35 @@ +fn factorial_loop() -> i64 { + let mut product = 1; + let mut i = 1; + + while i <= 10 { + product *= i; + i += 1; + } + + product +} + +fn index_for_loop() -> usize { + let mut sum = 0; + let a = [0, 10, 20, 30]; + for i in 0..a.len() { + sum += a[i]; + } + sum +} + +fn for_loop() -> usize { + let mut sum = 0; + let a = [0, 10, 20, 30]; + for &n in &a { + sum += n; + } + sum +} + +fn main() { + assert_eq!(factorial_loop(), 3628800); + assert_eq!(index_for_loop(), 60); + assert_eq!(for_loop(), 60); +} diff --git a/src/tools/miri/tests/pass/main_fn.rs b/src/tools/miri/tests/pass/main_fn.rs new file mode 100644 index 0000000000000..3b84d1abe6f3d --- /dev/null +++ b/src/tools/miri/tests/pass/main_fn.rs @@ -0,0 +1,7 @@ +#![feature(imported_main)] + +mod foo { + pub(crate) fn bar() {} +} + +use foo::bar as main; diff --git a/src/tools/miri/tests/pass/main_result.rs b/src/tools/miri/tests/pass/main_result.rs new file mode 100644 index 0000000000000..078760ee6667c --- /dev/null +++ b/src/tools/miri/tests/pass/main_result.rs @@ -0,0 +1,3 @@ +fn main() -> Result<(), Box> { + Ok(()) +} diff --git a/src/tools/miri/tests/pass/many_shr_bor.rs b/src/tools/miri/tests/pass/many_shr_bor.rs new file mode 100644 index 0000000000000..376b41dd6e209 --- /dev/null +++ b/src/tools/miri/tests/pass/many_shr_bor.rs @@ -0,0 +1,36 @@ +// Make sure validation can handle many overlapping shared borrows for different parts of a data structure +use std::cell::RefCell; + +#[allow(unused)] +struct Test { + a: u32, + b: u32, +} + +fn test1() { + let t = &mut Test { a: 0, b: 0 }; + { + let x; + { + let y = &t.a; + x = &t; + let _y = *y; + } + let _x = x.a; + } + t.b = 42; +} + +fn test2(r: &mut RefCell) { + let x = &*r; // releasing write lock, first suspension recorded + let mut x_ref = x.borrow_mut(); + let x_inner: &mut i32 = &mut *x_ref; // new inner write lock, with same lifetime as outer lock + let _x_inner_shr = &*x_inner; // releasing inner write lock, recording suspension + let _y = &*r; // second suspension for the outer write lock + let _x_inner_shr2 = &*x_inner; // 2nd suspension for inner write lock +} + +fn main() { + test1(); + test2(&mut RefCell::new(0)); +} diff --git a/src/tools/miri/tests/pass/match_slice.rs b/src/tools/miri/tests/pass/match_slice.rs new file mode 100644 index 0000000000000..e40a63ef2003b --- /dev/null +++ b/src/tools/miri/tests/pass/match_slice.rs @@ -0,0 +1,8 @@ +fn main() { + let x = "hello"; + match x { + "foo" => {} + "bar" => {} + _ => {} + } +} diff --git a/src/tools/miri/tests/pass/memchr.rs b/src/tools/miri/tests/pass/memchr.rs new file mode 100644 index 0000000000000..e92c37ff2a8c7 --- /dev/null +++ b/src/tools/miri/tests/pass/memchr.rs @@ -0,0 +1,89 @@ +#![feature(slice_internals)] + +use core::slice::memchr::{memchr, memrchr}; + +// test fallback implementations on all targets +fn matches_one() { + assert_eq!(Some(0), memchr(b'a', b"a")); +} + +fn matches_begin() { + assert_eq!(Some(0), memchr(b'a', b"aaaa")); +} + +fn matches_end() { + assert_eq!(Some(4), memchr(b'z', b"aaaaz")); +} + +fn matches_nul() { + assert_eq!(Some(4), memchr(b'\x00', b"aaaa\x00")); +} + +fn matches_past_nul() { + assert_eq!(Some(5), memchr(b'z', b"aaaa\x00z")); +} + +fn no_match_empty() { + assert_eq!(None, memchr(b'a', b"")); +} + +fn no_match() { + assert_eq!(None, memchr(b'a', b"xyz")); +} + +fn matches_one_reversed() { + assert_eq!(Some(0), memrchr(b'a', b"a")); +} + +fn matches_begin_reversed() { + assert_eq!(Some(3), memrchr(b'a', b"aaaa")); +} + +fn matches_end_reversed() { + assert_eq!(Some(0), memrchr(b'z', b"zaaaa")); +} + +fn matches_nul_reversed() { + assert_eq!(Some(4), memrchr(b'\x00', b"aaaa\x00")); +} + +fn matches_past_nul_reversed() { + assert_eq!(Some(0), memrchr(b'z', b"z\x00aaaa")); +} + +fn no_match_empty_reversed() { + assert_eq!(None, memrchr(b'a', b"")); +} + +fn no_match_reversed() { + assert_eq!(None, memrchr(b'a', b"xyz")); +} + +fn each_alignment_reversed() { + let mut data = [1u8; 64]; + let needle = 2; + let pos = 40; + data[pos] = needle; + for start in 0..16 { + assert_eq!(Some(pos - start), memrchr(needle, &data[start..])); + } +} + +fn main() { + matches_one(); + matches_begin(); + matches_end(); + matches_nul(); + matches_past_nul(); + no_match_empty(); + no_match(); + + matches_one_reversed(); + matches_begin_reversed(); + matches_end_reversed(); + matches_nul_reversed(); + matches_past_nul_reversed(); + no_match_empty_reversed(); + no_match_reversed(); + each_alignment_reversed(); +} diff --git a/src/tools/miri/tests/pass/memleak_ignored.rs b/src/tools/miri/tests/pass/memleak_ignored.rs new file mode 100644 index 0000000000000..60e06094e1771 --- /dev/null +++ b/src/tools/miri/tests/pass/memleak_ignored.rs @@ -0,0 +1,5 @@ +//@compile-flags: -Zmiri-ignore-leaks + +fn main() { + std::mem::forget(Box::new(42)); +} diff --git a/src/tools/miri/tests/pass/move-arg-2-unique.rs b/src/tools/miri/tests/pass/move-arg-2-unique.rs new file mode 100644 index 0000000000000..669602ac70436 --- /dev/null +++ b/src/tools/miri/tests/pass/move-arg-2-unique.rs @@ -0,0 +1,11 @@ +#![feature(box_syntax)] + +fn test(foo: Box>) { + assert_eq!((*foo)[0], 10); +} + +pub fn main() { + let x = box vec![10]; + // Test forgetting a local by move-in + test(x); +} diff --git a/src/tools/miri/tests/pass/move-arg-3-unique.rs b/src/tools/miri/tests/pass/move-arg-3-unique.rs new file mode 100644 index 0000000000000..3b5c7cbbd42ca --- /dev/null +++ b/src/tools/miri/tests/pass/move-arg-3-unique.rs @@ -0,0 +1,7 @@ +#![feature(box_syntax)] + +pub fn main() { + let x = box 10; + let y = x; + assert_eq!(*y, 10); +} diff --git a/src/tools/miri/tests/pass/move-uninit-primval.rs b/src/tools/miri/tests/pass/move-uninit-primval.rs new file mode 100644 index 0000000000000..f5fd27fa0d4ed --- /dev/null +++ b/src/tools/miri/tests/pass/move-uninit-primval.rs @@ -0,0 +1,14 @@ +#![allow(deprecated)] + +use std::mem; + +struct Foo { + _inner: mem::MaybeUninit, +} + +fn main() { + unsafe { + let foo = Foo { _inner: mem::uninitialized() }; + let _bar = foo; + } +} diff --git a/src/tools/miri/tests/pass/mpsc.rs b/src/tools/miri/tests/pass/mpsc.rs new file mode 100644 index 0000000000000..6e3c6e771ccb8 --- /dev/null +++ b/src/tools/miri/tests/pass/mpsc.rs @@ -0,0 +1,15 @@ +#![feature(box_syntax)] + +use std::sync::mpsc::channel; + +pub fn main() { + let (tx, rx) = channel::>(); + tx.send(box 100).unwrap(); + let v = rx.recv().unwrap(); + assert_eq!(v, box 100); + + tx.send(box 101).unwrap(); + tx.send(box 102).unwrap(); + assert_eq!(rx.recv().unwrap(), box 101); + assert_eq!(rx.recv().unwrap(), box 102); +} diff --git a/src/tools/miri/tests/pass/multi_arg_closure.rs b/src/tools/miri/tests/pass/multi_arg_closure.rs new file mode 100644 index 0000000000000..02d53540b83c3 --- /dev/null +++ b/src/tools/miri/tests/pass/multi_arg_closure.rs @@ -0,0 +1,8 @@ +fn foo(f: &mut dyn FnMut(isize, isize) -> isize) -> isize { + f(1, 2) +} + +fn main() { + let z = foo(&mut |x, y| x * 10 + y); + assert_eq!(z, 12); +} diff --git a/src/tools/miri/tests/pass/negative_discriminant.rs b/src/tools/miri/tests/pass/negative_discriminant.rs new file mode 100644 index 0000000000000..5a58deeac0f31 --- /dev/null +++ b/src/tools/miri/tests/pass/negative_discriminant.rs @@ -0,0 +1,16 @@ +enum AB { + A = -1, + B = 1, +} + +fn main() { + match AB::A { + AB::A => (), + AB::B => panic!(), + } + + match AB::B { + AB::A => panic!(), + AB::B => (), + } +} diff --git a/src/tools/miri/tests/pass/no_std.rs b/src/tools/miri/tests/pass/no_std.rs new file mode 100644 index 0000000000000..10632c2cce497 --- /dev/null +++ b/src/tools/miri/tests/pass/no_std.rs @@ -0,0 +1,21 @@ +#![feature(lang_items, start)] +#![no_std] +// windows tls dtors go through libstd right now, thus this test +// cannot pass. When windows tls dtors go through the special magic +// windows linker section, we can run this test on windows again. +//@ignore-target-windows + +#[start] +fn start(_: isize, _: *const *const u8) -> isize { + for _ in 0..10 {} + + 0 +} + +#[panic_handler] +fn panic_handler(_: &core::panic::PanicInfo) -> ! { + loop {} +} + +#[lang = "eh_personality"] +fn eh_personality() {} diff --git a/src/tools/miri/tests/pass/observed_local_mut.rs b/src/tools/miri/tests/pass/observed_local_mut.rs new file mode 100644 index 0000000000000..ca0f569860b82 --- /dev/null +++ b/src/tools/miri/tests/pass/observed_local_mut.rs @@ -0,0 +1,24 @@ +// Stacked Borrows catches this (correctly) as UB. +//@compile-flags: -Zmiri-disable-stacked-borrows + +// This test is intended to guard against the problem described in commit +// 39bb1254d1eaf74f45a4e741097e33fc942168d5. +// +// As written, it might be considered UB in compiled Rust, but of course Miri gives it a safe, +// deterministic behaviour (one that might not correspond with how an eventual Rust spec would +// defined this). +// +// An alternative way to write the test without `unsafe` would be to use `Cell`, but it would +// only surface the bug described by the above commit if `Cell` on the stack got represented +// as a primitive `PrimVal::I32` which is not yet the case. + +fn main() { + let mut x = 0; + let y: *const i32 = &x; + x = 1; + + // When the described bug is in place, this results in `0`, not observing the `x = 1` line. + assert_eq!(unsafe { *y }, 1); + + assert_eq!(x, 1); +} diff --git a/src/tools/miri/tests/pass/option_box_transmute_ptr.rs b/src/tools/miri/tests/pass/option_box_transmute_ptr.rs new file mode 100644 index 0000000000000..0786db1ef895a --- /dev/null +++ b/src/tools/miri/tests/pass/option_box_transmute_ptr.rs @@ -0,0 +1,15 @@ +// This tests that the size of Option> is the same as *const i32. +fn option_box_deref() -> i32 { + let val = Some(Box::new(42)); + unsafe { + let ptr: *const i32 = std::mem::transmute::>, *const i32>(val); + let ret = *ptr; + // unleak memory + std::mem::transmute::<*const i32, Option>>(ptr); + ret + } +} + +fn main() { + assert_eq!(option_box_deref(), 42); +} diff --git a/src/tools/miri/tests/pass/option_eq.rs b/src/tools/miri/tests/pass/option_eq.rs new file mode 100644 index 0000000000000..e698f8767746c --- /dev/null +++ b/src/tools/miri/tests/pass/option_eq.rs @@ -0,0 +1,3 @@ +fn main() { + assert_eq!(std::char::from_u32('x' as u32), Some('x')); +} diff --git a/src/tools/miri/tests/pass/overflow_checks_off.rs b/src/tools/miri/tests/pass/overflow_checks_off.rs new file mode 100644 index 0000000000000..79aa510ef97fe --- /dev/null +++ b/src/tools/miri/tests/pass/overflow_checks_off.rs @@ -0,0 +1,18 @@ +//@compile-flags: -C overflow-checks=off + +// Check that we correctly implement the intended behavior of these operators +// when they are not being overflow-checked. + +// FIXME: if we call the functions in `std::ops`, we still get the panics. +// Miri does not implement the codegen-time hack that backs `#[rustc_inherit_overflow_checks]`. +// use std::ops::*; + +fn main() { + assert_eq!(-{ -0x80i8 }, -0x80); + + assert_eq!(0xffu8 + 1, 0_u8); + assert_eq!(0u8 - 1, 0xff_u8); + assert_eq!(0xffu8 * 2, 0xfe_u8); + assert_eq!(1u8 << 9, 2_u8); + assert_eq!(2u8 >> 9, 1_u8); +} diff --git a/src/tools/miri/tests/pass/overloaded-calls-simple.rs b/src/tools/miri/tests/pass/overloaded-calls-simple.rs new file mode 100644 index 0000000000000..9fcf7d4a819a5 --- /dev/null +++ b/src/tools/miri/tests/pass/overloaded-calls-simple.rs @@ -0,0 +1,19 @@ +#![feature(lang_items, unboxed_closures, fn_traits)] + +struct S3 { + x: i32, + y: i32, +} + +impl FnOnce<(i32, i32)> for S3 { + type Output = i32; + extern "rust-call" fn call_once(self, (z, zz): (i32, i32)) -> i32 { + self.x * self.y * z * zz + } +} + +fn main() { + let s = S3 { x: 3, y: 3 }; + let ans = s(3, 1); + assert_eq!(ans, 27); +} diff --git a/src/tools/miri/tests/pass/packed_struct.rs b/src/tools/miri/tests/pass/packed_struct.rs new file mode 100644 index 0000000000000..85acab858aab9 --- /dev/null +++ b/src/tools/miri/tests/pass/packed_struct.rs @@ -0,0 +1,151 @@ +#![feature(unsize, coerce_unsized)] + +use std::collections::hash_map::DefaultHasher; +use std::hash::Hash; +use std::ptr; + +fn test_basic() { + #[repr(packed)] + struct S { + fill: u8, + a: i32, + b: i64, + } + + #[repr(packed)] + #[allow(dead_code)] + struct Test1<'a> { + x: u8, + other: &'a u32, + } + + #[repr(packed)] + #[allow(dead_code)] + struct Test2<'a> { + x: u8, + other: &'a Test1<'a>, + } + + fn test(t: Test2) { + let x = *t.other.other; + assert_eq!(x, 42); + } + + let mut x = S { fill: 0, a: 42, b: 99 }; + let a = x.a; + let b = x.b; + assert_eq!(a, 42); + assert_eq!(b, 99); + assert_eq!(&x.fill, &0); // `fill` just requirs 1-byte-align, so this is fine + // can't do `assert_eq!(x.a, 42)`, because `assert_eq!` takes a reference + assert_eq!({ x.a }, 42); + assert_eq!({ x.b }, 99); + // but we *can* take a raw pointer! + assert_eq!(unsafe { ptr::addr_of!(x.a).read_unaligned() }, 42); + assert_eq!(unsafe { ptr::addr_of!(x.b).read_unaligned() }, 99); + + x.b = 77; + assert_eq!({ x.b }, 77); + + test(Test2 { x: 0, other: &Test1 { x: 0, other: &42 } }); +} + +fn test_unsizing() { + #[repr(packed)] + #[allow(dead_code)] + struct UnalignedPtr<'a, T: ?Sized> + where + T: 'a, + { + data: &'a T, + } + + impl<'a, T, U> std::ops::CoerceUnsized> for UnalignedPtr<'a, T> + where + T: std::marker::Unsize + ?Sized, + U: ?Sized, + { + } + + let arr = [1, 2, 3]; + let arr_unaligned: UnalignedPtr<[i32; 3]> = UnalignedPtr { data: &arr }; + let arr_unaligned: UnalignedPtr<[i32]> = arr_unaligned; + let _unused = &arr_unaligned; // forcing an allocation, which could also yield "unaligned write"-errors +} + +fn test_drop() { + struct Wrap(u32); + impl Drop for Wrap { + fn drop(&mut self) { + // Do an (aligned) load + let _test = self.0; + // For the fun of it, test alignment + assert_eq!(&self.0 as *const _ as usize % std::mem::align_of::(), 0); + } + } + + #[repr(packed, C)] + struct Packed { + f1: u8, // this should move the second field to something not very aligned + f2: T, + } + + let p = Packed { f1: 42, f2: Wrap(23) }; + drop(p); +} + +fn test_inner_packed() { + // Even if just the inner struct is packed, accesses to the outer field can get unaligned. + // Make sure that works. + #[repr(packed)] + #[derive(Clone, Copy)] + struct Inner(u32); + + #[derive(Clone, Copy)] + struct Outer(u8, Inner); + + let o = Outer(0, Inner(42)); + let _x = o.1; + let _y = (o.1).0; + let _o2 = o.clone(); +} + +fn test_static() { + #[repr(packed)] + struct Foo { + i: i32, + } + + static FOO: Foo = Foo { i: 42 }; + + assert_eq!({ FOO.i }, 42); +} + +fn test_derive() { + #[repr(packed)] + #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] + struct P { + a: usize, + b: u8, + c: usize, + } + + let x = P { a: 1usize, b: 2u8, c: 3usize }; + let y = P { a: 1usize, b: 2u8, c: 4usize }; + + let _clone = x.clone(); + assert!(x != y); + assert_eq!(x.partial_cmp(&y).unwrap(), x.cmp(&y)); + x.hash(&mut DefaultHasher::new()); + P::default(); + format!("{:?}", x); +} + +fn main() { + test_basic(); + test_unsizing(); + test_drop(); + test_inner_packed(); + test_static(); + test_derive(); +} diff --git a/src/tools/miri/tests/pass/panic/catch_panic.rs b/src/tools/miri/tests/pass/panic/catch_panic.rs new file mode 100644 index 0000000000000..308904406538c --- /dev/null +++ b/src/tools/miri/tests/pass/panic/catch_panic.rs @@ -0,0 +1,119 @@ +// We test the `align_offset` panic below, make sure we test the interpreter impl and not the "real" one. +//@compile-flags: -Zmiri-symbolic-alignment-check -Zmiri-permissive-provenance +#![feature(never_type)] +#![allow(unconditional_panic, non_fmt_panics)] + +use std::cell::Cell; +use std::panic::{catch_unwind, AssertUnwindSafe}; + +thread_local! { + static MY_COUNTER: Cell = Cell::new(0); + static DROPPED: Cell = Cell::new(false); + static HOOK_CALLED: Cell = Cell::new(false); +} + +struct DropTester; +impl Drop for DropTester { + fn drop(&mut self) { + DROPPED.with(|c| { + c.set(true); + }); + } +} + +fn do_panic_counter(do_panic: impl FnOnce(usize) -> !) { + // If this gets leaked, it will be easy to spot + // in Miri's leak report + let _string = "LEAKED FROM do_panic_counter".to_string(); + + // When we panic, this should get dropped during unwinding + let _drop_tester = DropTester; + + // Check for bugs in Miri's panic implementation. + // If do_panic_counter() somehow gets called more than once, + // we'll generate a different panic message and stderr will differ. + let old_val = MY_COUNTER.with(|c| { + let val = c.get(); + c.set(val + 1); + val + }); + do_panic(old_val); +} + +fn main() { + let prev = std::panic::take_hook(); + std::panic::set_hook(Box::new(move |panic_info| { + HOOK_CALLED.with(|h| h.set(true)); + prev(panic_info) + })); + + // Std panics + test(None, |_old_val| std::panic!("Hello from panic: std")); + test(None, |old_val| std::panic!(format!("Hello from panic: {:?}", old_val))); + test(None, |old_val| std::panic!("Hello from panic: {:?}", old_val)); + test(None, |_old_val| std::panic!(1337)); + + // Core panics + test(None, |_old_val| core::panic!("Hello from panic: core")); + test(None, |old_val| core::panic!(&format!("Hello from panic: {:?}", old_val))); + test(None, |old_val| core::panic!("Hello from panic: {:?}", old_val)); + + // Built-in panics; also make sure the message is right. + test(Some("index out of bounds: the len is 3 but the index is 4"), |_old_val| { + let _val = [0, 1, 2][4]; + loop {} + }); + test(Some("attempt to divide by zero"), |_old_val| { + let _val = 1 / 0; + loop {} + }); + + test(Some("align_offset: align is not a power-of-two"), |_old_val| { + (0usize as *const u8).align_offset(3); + loop {} + }); + + // Assertion and debug assertion + test(None, |_old_val| { + assert!(false); + loop {} + }); + test(None, |_old_val| { + debug_assert!(false); + loop {} + }); + + eprintln!("Success!"); // Make sure we get this in stderr +} + +fn test(expect_msg: Option<&str>, do_panic: impl FnOnce(usize) -> !) { + // Reset test flags. + DROPPED.with(|c| c.set(false)); + HOOK_CALLED.with(|c| c.set(false)); + + // Cause and catch a panic. + let res = catch_unwind(AssertUnwindSafe(|| { + let _string = "LEAKED FROM CLOSURE".to_string(); + do_panic_counter(do_panic) + })) + .expect_err("do_panic() did not panic!"); + + // See if we can extract the panic message. + let msg = if let Some(s) = res.downcast_ref::() { + eprintln!("Caught panic message (String): {}", s); + Some(s.as_str()) + } else if let Some(s) = res.downcast_ref::<&str>() { + eprintln!("Caught panic message (&str): {}", s); + Some(*s) + } else { + eprintln!("Failed to get caught panic message."); + None + }; + if let Some(expect_msg) = expect_msg { + assert_eq!(expect_msg, msg.unwrap()); + } + + // Test flags. + assert!(DROPPED.with(|c| c.get())); + assert!(HOOK_CALLED.with(|c| c.get())); +} diff --git a/src/tools/miri/tests/pass/panic/catch_panic.stderr b/src/tools/miri/tests/pass/panic/catch_panic.stderr new file mode 100644 index 0000000000000..0ced5588cc12c --- /dev/null +++ b/src/tools/miri/tests/pass/panic/catch_panic.stderr @@ -0,0 +1,26 @@ +thread 'main' panicked at 'Hello from panic: std', $DIR/catch_panic.rs:LL:CC +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +Caught panic message (&str): Hello from panic: std +thread 'main' panicked at 'Hello from panic: 1', $DIR/catch_panic.rs:LL:CC +Caught panic message (String): Hello from panic: 1 +thread 'main' panicked at 'Hello from panic: 2', $DIR/catch_panic.rs:LL:CC +Caught panic message (String): Hello from panic: 2 +thread 'main' panicked at 'Box', $DIR/catch_panic.rs:LL:CC +Failed to get caught panic message. +thread 'main' panicked at 'Hello from panic: core', $DIR/catch_panic.rs:LL:CC +Caught panic message (&str): Hello from panic: core +thread 'main' panicked at 'Hello from panic: 5', $DIR/catch_panic.rs:LL:CC +Caught panic message (String): Hello from panic: 5 +thread 'main' panicked at 'Hello from panic: 6', $DIR/catch_panic.rs:LL:CC +Caught panic message (String): Hello from panic: 6 +thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 4', $DIR/catch_panic.rs:LL:CC +Caught panic message (String): index out of bounds: the len is 3 but the index is 4 +thread 'main' panicked at 'attempt to divide by zero', $DIR/catch_panic.rs:LL:CC +Caught panic message (&str): attempt to divide by zero +thread 'main' panicked at 'align_offset: align is not a power-of-two', RUSTLIB/core/src/ptr/const_ptr.rs:LL:CC +Caught panic message (&str): align_offset: align is not a power-of-two +thread 'main' panicked at 'assertion failed: false', $DIR/catch_panic.rs:LL:CC +Caught panic message (&str): assertion failed: false +thread 'main' panicked at 'assertion failed: false', $DIR/catch_panic.rs:LL:CC +Caught panic message (&str): assertion failed: false +Success! diff --git a/src/tools/miri/tests/pass/panic/concurrent-panic.rs b/src/tools/miri/tests/pass/panic/concurrent-panic.rs new file mode 100644 index 0000000000000..342269c6acbe3 --- /dev/null +++ b/src/tools/miri/tests/pass/panic/concurrent-panic.rs @@ -0,0 +1,93 @@ +//@ignore-target-windows: Condvars on Windows are not supported yet. +// We are making scheduler assumptions here. +//@compile-flags: -Zmiri-preemption-rate=0 + +//! Cause a panic in one thread while another thread is unwinding. This checks +//! that separate threads have their own panicking state. + +use std::sync::{Arc, Condvar, Mutex}; +use std::thread::{spawn, JoinHandle}; + +struct BlockOnDrop(Option>); + +impl BlockOnDrop { + fn new(handle: JoinHandle<()>) -> BlockOnDrop { + BlockOnDrop(Some(handle)) + } +} + +impl Drop for BlockOnDrop { + fn drop(&mut self) { + eprintln!("Thread 2 blocking on thread 1"); + let _ = self.0.take().unwrap().join(); + eprintln!("Thread 1 has exited"); + } +} + +fn main() { + let t1_started_pair = Arc::new((Mutex::new(false), Condvar::new())); + let t2_started_pair = Arc::new((Mutex::new(false), Condvar::new())); + + let t1_continue_mutex = Arc::new(Mutex::new(())); + let t1_continue_guard = t1_continue_mutex.lock(); + + let t1 = { + let t1_started_pair = t1_started_pair.clone(); + let t1_continue_mutex = t1_continue_mutex.clone(); + spawn(move || { + eprintln!("Thread 1 starting, will block on mutex"); + let (mutex, condvar) = &*t1_started_pair; + *mutex.lock().unwrap() = true; + condvar.notify_one(); + + drop(t1_continue_mutex.lock()); + panic!("panic in thread 1"); + }) + }; + + // Wait for thread 1 to signal it has started. + let (t1_started_mutex, t1_started_condvar) = &*t1_started_pair; + let mut t1_started_guard = t1_started_mutex.lock().unwrap(); + while !*t1_started_guard { + t1_started_guard = t1_started_condvar.wait(t1_started_guard).unwrap(); + } + eprintln!("Thread 1 reported it has started"); + // Thread 1 should now be blocked waiting on t1_continue_mutex. + + let t2 = { + let t2_started_pair = t2_started_pair.clone(); + let block_on_drop = BlockOnDrop::new(t1); + spawn(move || { + let _ = block_on_drop; + + let (mutex, condvar) = &*t2_started_pair; + *mutex.lock().unwrap() = true; + condvar.notify_one(); + + panic!("panic in thread 2"); + }) + }; + + // Wait for thread 2 to signal it has started. + let (t2_started_mutex, t2_started_condvar) = &*t2_started_pair; + let mut t2_started_guard = t2_started_mutex.lock().unwrap(); + while !*t2_started_guard { + t2_started_guard = t2_started_condvar.wait(t2_started_guard).unwrap(); + } + eprintln!("Thread 2 reported it has started"); + // Thread 2 should now have already panicked and be in the middle of + // unwinding. It should now be blocked on joining thread 1. + + // Unlock t1_continue_mutex, and allow thread 1 to proceed. + eprintln!("Unlocking mutex"); + drop(t1_continue_guard); + // Thread 1 will panic the next time it is scheduled. This will test the + // behavior of interest to this test, whether Miri properly handles + // concurrent panics in two different threads. + + // Block the main thread on waiting to join thread 2. Thread 2 should + // already be blocked on joining thread 1, so thread 1 will be scheduled + // to run next, as it is the only ready thread. + assert!(t2.join().is_err()); + eprintln!("Thread 2 has exited"); +} diff --git a/src/tools/miri/tests/pass/panic/concurrent-panic.stderr b/src/tools/miri/tests/pass/panic/concurrent-panic.stderr new file mode 100644 index 0000000000000..fd8fabc89cccf --- /dev/null +++ b/src/tools/miri/tests/pass/panic/concurrent-panic.stderr @@ -0,0 +1,10 @@ +Thread 1 starting, will block on mutex +Thread 1 reported it has started +thread '' panicked at 'panic in thread 2', $DIR/concurrent-panic.rs:LL:CC +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +Thread 2 blocking on thread 1 +Thread 2 reported it has started +Unlocking mutex +thread '' panicked at 'panic in thread 1', $DIR/concurrent-panic.rs:LL:CC +Thread 1 has exited +Thread 2 has exited diff --git a/src/tools/miri/tests/pass/panic/std-panic-locations.rs b/src/tools/miri/tests/pass/panic/std-panic-locations.rs new file mode 100644 index 0000000000000..8781a371d51fa --- /dev/null +++ b/src/tools/miri/tests/pass/panic/std-panic-locations.rs @@ -0,0 +1,39 @@ +//! Test that panic locations for `#[track_caller]` functions in std have the correct +//! location reported. + +use std::sync::atomic::{AtomicUsize, Ordering}; + +static HOOK_COUNT: AtomicUsize = AtomicUsize::new(0); + +fn main() { + // inspect the `PanicInfo` we receive to ensure the right file is the source + std::panic::set_hook(Box::new(|info| { + HOOK_COUNT.fetch_add(1, Ordering::Relaxed); + let actual = info.location().unwrap(); + if actual.file() != file!() { + eprintln!("expected a location in the test file, found {:?}", actual); + panic!(); + } + })); + + fn assert_panicked(f: impl FnOnce() + std::panic::UnwindSafe) { + std::panic::catch_unwind(f).unwrap_err(); + } + + let nope: Option<()> = None; + assert_panicked(|| nope.unwrap()); + assert_panicked(|| nope.expect("")); + + let oops: Result<(), ()> = Err(()); + assert_panicked(|| oops.unwrap()); + assert_panicked(|| oops.expect("")); + + let fine: Result<(), ()> = Ok(()); + assert_panicked(|| fine.unwrap_err()); + assert_panicked(|| fine.expect_err("")); + + // Cleanup: reset to default hook. + drop(std::panic::take_hook()); + + assert_eq!(HOOK_COUNT.load(Ordering::Relaxed), 6); +} diff --git a/src/tools/miri/tests/pass/partially-uninit.rs b/src/tools/miri/tests/pass/partially-uninit.rs new file mode 100644 index 0000000000000..db26a2084b1c0 --- /dev/null +++ b/src/tools/miri/tests/pass/partially-uninit.rs @@ -0,0 +1,15 @@ +use std::mem::{self, MaybeUninit}; + +#[repr(C)] +#[derive(Copy, Clone, Debug, PartialEq)] +struct Demo(bool, u16); + +fn main() { + unsafe { + // Transmute-round-trip through a type with Scalar layout is lossless. + // This is tricky because that 'scalar' is *partially* uninitialized. + let x = Demo(true, 3); + let y: MaybeUninit = mem::transmute(x); + assert_eq!(x, mem::transmute(y)); + } +} diff --git a/src/tools/miri/tests/pass/pointers.rs b/src/tools/miri/tests/pass/pointers.rs new file mode 100644 index 0000000000000..d1340a04e0498 --- /dev/null +++ b/src/tools/miri/tests/pass/pointers.rs @@ -0,0 +1,143 @@ +//@compile-flags: -Zmiri-permissive-provenance +#![feature(ptr_metadata, const_raw_ptr_comparison)] + +use std::mem::{self, transmute}; +use std::ptr; + +fn one_line_ref() -> i16 { + *&1 +} + +fn basic_ref() -> i16 { + let x = &1; + *x +} + +fn basic_ref_mut() -> i16 { + let x = &mut 1; + *x += 2; + *x +} + +fn basic_ref_mut_var() -> i16 { + let mut a = 1; + { + let x = &mut a; + *x += 2; + } + a +} + +fn tuple_ref_mut() -> (i8, i8) { + let mut t = (10, 20); + { + let x = &mut t.1; + *x += 2; + } + t +} + +fn match_ref_mut() -> i8 { + let mut t = (20, 22); + { + let opt = Some(&mut t); + match opt { + Some(&mut (ref mut x, ref mut y)) => *x += *y, + None => {} + } + } + t.0 +} + +fn dangling_pointer() -> *const i32 { + let b = Box::new((42, 42)); // make it bigger than the alignment, so that there is some "room" after this pointer + &b.0 as *const i32 +} + +fn wide_ptr_ops() { + let a: *const dyn Send = &1 as &dyn Send; + let b: *const dyn Send = &1 as &dyn Send; + let _val = a == b; + let _val = a != b; + let _val = a < b; + let _val = a <= b; + let _val = a > b; + let _val = a >= b; + + let a: *const [u8] = unsafe { transmute((1usize, 1usize)) }; + let b: *const [u8] = unsafe { transmute((1usize, 2usize)) }; + // confirmed with rustc. + assert!(!(a == b)); + assert!(a != b); + assert!(a <= b); + assert!(a < b); + assert!(!(a >= b)); + assert!(!(a > b)); +} + +fn metadata_vtable() { + let p = &0i32 as &dyn std::fmt::Debug; + let meta: ptr::DynMetadata<_> = ptr::metadata(p as *const _); + assert_eq!(meta.size_of(), mem::size_of::()); + assert_eq!(meta.align_of(), mem::align_of::()); + + type T = [i32; 16]; + let p = &T::default() as &dyn std::fmt::Debug; + let meta: ptr::DynMetadata<_> = ptr::metadata(p as *const _); + assert_eq!(meta.size_of(), mem::size_of::()); + assert_eq!(meta.align_of(), mem::align_of::()); +} + +fn main() { + assert_eq!(one_line_ref(), 1); + assert_eq!(basic_ref(), 1); + assert_eq!(basic_ref_mut(), 3); + assert_eq!(basic_ref_mut_var(), 3); + assert_eq!(tuple_ref_mut(), (10, 22)); + assert_eq!(match_ref_mut(), 42); + + // Compare even dangling pointers with NULL, and with others in the same allocation, including + // out-of-bounds. + assert!(dangling_pointer() != std::ptr::null()); + assert!(match dangling_pointer() as usize { + 0 => false, + _ => true, + }); + let dangling = dangling_pointer(); + assert!(dangling == dangling); + assert!(dangling.wrapping_add(1) != dangling); + assert!(dangling.wrapping_sub(1) != dangling); + + // Compare pointer with BIG integers + let dangling = dangling as usize; + assert!(dangling != usize::MAX); + assert!(dangling != usize::MAX - 1); + assert!(dangling != usize::MAX - 2); + assert!(dangling != usize::MAX - 3); // this is even 4-aligned, but it still cannot be equal because of the extra "room" after this pointer + assert_eq!((usize::MAX - 3) % 4, 0); // just to be sure we got this right + + // Compare pointer with unaligned integers + assert!(dangling != 1usize); + assert!(dangling != 2usize); + assert!(dangling != 3usize); + // 4 is a possible choice! So we cannot compare with that. + assert!(dangling != 5usize); + assert!(dangling != 6usize); + assert!(dangling != 7usize); + + // Using inequality to do the comparison. + assert!(dangling > 0); + assert!(dangling > 1); + assert!(dangling > 2); + assert!(dangling > 3); + assert!(dangling >= 4); + + // CTFE-specific equality tests, need to also work at runtime. + let addr = &13 as *const i32; + let addr2 = (addr as usize).wrapping_add(usize::MAX).wrapping_add(1); + assert_eq!(addr.guaranteed_eq(addr2 as *const _), Some(true)); + assert_eq!(addr.guaranteed_ne(0x100 as *const _), Some(true)); + + wide_ptr_ops(); + metadata_vtable(); +} diff --git a/src/tools/miri/tests/pass/portable-simd.rs b/src/tools/miri/tests/pass/portable-simd.rs new file mode 100644 index 0000000000000..173ac654b03d5 --- /dev/null +++ b/src/tools/miri/tests/pass/portable-simd.rs @@ -0,0 +1,396 @@ +//@compile-flags: -Zmiri-strict-provenance +#![feature(portable_simd, platform_intrinsics)] +use std::simd::*; + +fn simd_ops_f32() { + let a = f32x4::splat(10.0); + let b = f32x4::from_array([1.0, 2.0, 3.0, -4.0]); + assert_eq!(-b, f32x4::from_array([-1.0, -2.0, -3.0, 4.0])); + assert_eq!(a + b, f32x4::from_array([11.0, 12.0, 13.0, 6.0])); + assert_eq!(a - b, f32x4::from_array([9.0, 8.0, 7.0, 14.0])); + assert_eq!(a * b, f32x4::from_array([10.0, 20.0, 30.0, -40.0])); + assert_eq!(b / a, f32x4::from_array([0.1, 0.2, 0.3, -0.4])); + assert_eq!(a / f32x4::splat(2.0), f32x4::splat(5.0)); + assert_eq!(a % b, f32x4::from_array([0.0, 0.0, 1.0, 2.0])); + assert_eq!(b.abs(), f32x4::from_array([1.0, 2.0, 3.0, 4.0])); + assert_eq!(a.simd_max(b * f32x4::splat(4.0)), f32x4::from_array([10.0, 10.0, 12.0, 10.0])); + assert_eq!(a.simd_min(b * f32x4::splat(4.0)), f32x4::from_array([4.0, 8.0, 10.0, -16.0])); + + assert_eq!(a.mul_add(b, a), (a * b) + a); + assert_eq!(b.mul_add(b, a), (b * b) + a); + assert_eq!(a.mul_add(b, b), (a * b) + b); + assert_eq!( + f32x4::splat(-3.2).mul_add(b, f32x4::splat(f32::NEG_INFINITY)), + f32x4::splat(f32::NEG_INFINITY) + ); + assert_eq!((a * a).sqrt(), a); + assert_eq!((b * b).sqrt(), b.abs()); + + assert_eq!(a.simd_eq(f32x4::splat(5.0) * b), Mask::from_array([false, true, false, false])); + assert_eq!(a.simd_ne(f32x4::splat(5.0) * b), Mask::from_array([true, false, true, true])); + assert_eq!(a.simd_le(f32x4::splat(5.0) * b), Mask::from_array([false, true, true, false])); + assert_eq!(a.simd_lt(f32x4::splat(5.0) * b), Mask::from_array([false, false, true, false])); + assert_eq!(a.simd_ge(f32x4::splat(5.0) * b), Mask::from_array([true, true, false, true])); + assert_eq!(a.simd_gt(f32x4::splat(5.0) * b), Mask::from_array([true, false, false, true])); + + assert_eq!(a.reduce_sum(), 40.0); + assert_eq!(b.reduce_sum(), 2.0); + assert_eq!(a.reduce_product(), 100.0 * 100.0); + assert_eq!(b.reduce_product(), -24.0); + assert_eq!(a.reduce_max(), 10.0); + assert_eq!(b.reduce_max(), 3.0); + assert_eq!(a.reduce_min(), 10.0); + assert_eq!(b.reduce_min(), -4.0); + + assert_eq!( + f32x2::from_array([0.0, f32::NAN]).simd_max(f32x2::from_array([f32::NAN, 0.0])), + f32x2::from_array([0.0, 0.0]) + ); + assert_eq!(f32x2::from_array([0.0, f32::NAN]).reduce_max(), 0.0); + assert_eq!(f32x2::from_array([f32::NAN, 0.0]).reduce_max(), 0.0); + assert_eq!( + f32x2::from_array([0.0, f32::NAN]).simd_min(f32x2::from_array([f32::NAN, 0.0])), + f32x2::from_array([0.0, 0.0]) + ); + assert_eq!(f32x2::from_array([0.0, f32::NAN]).reduce_min(), 0.0); + assert_eq!(f32x2::from_array([f32::NAN, 0.0]).reduce_min(), 0.0); +} + +fn simd_ops_f64() { + let a = f64x4::splat(10.0); + let b = f64x4::from_array([1.0, 2.0, 3.0, -4.0]); + assert_eq!(-b, f64x4::from_array([-1.0, -2.0, -3.0, 4.0])); + assert_eq!(a + b, f64x4::from_array([11.0, 12.0, 13.0, 6.0])); + assert_eq!(a - b, f64x4::from_array([9.0, 8.0, 7.0, 14.0])); + assert_eq!(a * b, f64x4::from_array([10.0, 20.0, 30.0, -40.0])); + assert_eq!(b / a, f64x4::from_array([0.1, 0.2, 0.3, -0.4])); + assert_eq!(a / f64x4::splat(2.0), f64x4::splat(5.0)); + assert_eq!(a % b, f64x4::from_array([0.0, 0.0, 1.0, 2.0])); + assert_eq!(b.abs(), f64x4::from_array([1.0, 2.0, 3.0, 4.0])); + assert_eq!(a.simd_max(b * f64x4::splat(4.0)), f64x4::from_array([10.0, 10.0, 12.0, 10.0])); + assert_eq!(a.simd_min(b * f64x4::splat(4.0)), f64x4::from_array([4.0, 8.0, 10.0, -16.0])); + + assert_eq!(a.mul_add(b, a), (a * b) + a); + assert_eq!(b.mul_add(b, a), (b * b) + a); + assert_eq!(a.mul_add(b, b), (a * b) + b); + assert_eq!( + f64x4::splat(-3.2).mul_add(b, f64x4::splat(f64::NEG_INFINITY)), + f64x4::splat(f64::NEG_INFINITY) + ); + assert_eq!((a * a).sqrt(), a); + assert_eq!((b * b).sqrt(), b.abs()); + + assert_eq!(a.simd_eq(f64x4::splat(5.0) * b), Mask::from_array([false, true, false, false])); + assert_eq!(a.simd_ne(f64x4::splat(5.0) * b), Mask::from_array([true, false, true, true])); + assert_eq!(a.simd_le(f64x4::splat(5.0) * b), Mask::from_array([false, true, true, false])); + assert_eq!(a.simd_lt(f64x4::splat(5.0) * b), Mask::from_array([false, false, true, false])); + assert_eq!(a.simd_ge(f64x4::splat(5.0) * b), Mask::from_array([true, true, false, true])); + assert_eq!(a.simd_gt(f64x4::splat(5.0) * b), Mask::from_array([true, false, false, true])); + + assert_eq!(a.reduce_sum(), 40.0); + assert_eq!(b.reduce_sum(), 2.0); + assert_eq!(a.reduce_product(), 100.0 * 100.0); + assert_eq!(b.reduce_product(), -24.0); + assert_eq!(a.reduce_max(), 10.0); + assert_eq!(b.reduce_max(), 3.0); + assert_eq!(a.reduce_min(), 10.0); + assert_eq!(b.reduce_min(), -4.0); + + assert_eq!( + f64x2::from_array([0.0, f64::NAN]).simd_max(f64x2::from_array([f64::NAN, 0.0])), + f64x2::from_array([0.0, 0.0]) + ); + assert_eq!(f64x2::from_array([0.0, f64::NAN]).reduce_max(), 0.0); + assert_eq!(f64x2::from_array([f64::NAN, 0.0]).reduce_max(), 0.0); + assert_eq!( + f64x2::from_array([0.0, f64::NAN]).simd_min(f64x2::from_array([f64::NAN, 0.0])), + f64x2::from_array([0.0, 0.0]) + ); + assert_eq!(f64x2::from_array([0.0, f64::NAN]).reduce_min(), 0.0); + assert_eq!(f64x2::from_array([f64::NAN, 0.0]).reduce_min(), 0.0); +} + +fn simd_ops_i32() { + let a = i32x4::splat(10); + let b = i32x4::from_array([1, 2, 3, -4]); + assert_eq!(-b, i32x4::from_array([-1, -2, -3, 4])); + assert_eq!(a + b, i32x4::from_array([11, 12, 13, 6])); + assert_eq!(a - b, i32x4::from_array([9, 8, 7, 14])); + assert_eq!(a * b, i32x4::from_array([10, 20, 30, -40])); + assert_eq!(a / b, i32x4::from_array([10, 5, 3, -2])); + assert_eq!(a / i32x4::splat(2), i32x4::splat(5)); + assert_eq!(i32x2::splat(i32::MIN) / i32x2::splat(-1), i32x2::splat(i32::MIN)); + assert_eq!(a % b, i32x4::from_array([0, 0, 1, 2])); + assert_eq!(i32x2::splat(i32::MIN) % i32x2::splat(-1), i32x2::splat(0)); + assert_eq!(b.abs(), i32x4::from_array([1, 2, 3, 4])); + assert_eq!(a.simd_max(b * i32x4::splat(4)), i32x4::from_array([10, 10, 12, 10])); + assert_eq!(a.simd_min(b * i32x4::splat(4)), i32x4::from_array([4, 8, 10, -16])); + + assert_eq!( + i8x4::from_array([i8::MAX, -23, 23, i8::MIN]).saturating_add(i8x4::from_array([ + 1, + i8::MIN, + i8::MAX, + 28 + ])), + i8x4::from_array([i8::MAX, i8::MIN, i8::MAX, -100]) + ); + assert_eq!( + i8x4::from_array([i8::MAX, -28, 27, 42]).saturating_sub(i8x4::from_array([ + 1, + i8::MAX, + i8::MAX, + -80 + ])), + i8x4::from_array([126, i8::MIN, -100, 122]) + ); + assert_eq!( + u8x4::from_array([u8::MAX, 0, 23, 42]).saturating_add(u8x4::from_array([ + 1, + 1, + u8::MAX, + 200 + ])), + u8x4::from_array([u8::MAX, 1, u8::MAX, 242]) + ); + assert_eq!( + u8x4::from_array([u8::MAX, 0, 23, 42]).saturating_sub(u8x4::from_array([ + 1, + 1, + u8::MAX, + 200 + ])), + u8x4::from_array([254, 0, 0, 0]) + ); + + assert_eq!(!b, i32x4::from_array([!1, !2, !3, !-4])); + assert_eq!(b << i32x4::splat(2), i32x4::from_array([4, 8, 12, -16])); + assert_eq!(b >> i32x4::splat(1), i32x4::from_array([0, 1, 1, -2])); + assert_eq!(b & i32x4::splat(2), i32x4::from_array([0, 2, 2, 0])); + assert_eq!(b | i32x4::splat(2), i32x4::from_array([3, 2, 3, -2])); + assert_eq!(b ^ i32x4::splat(2), i32x4::from_array([3, 0, 1, -2])); + + assert_eq!(a.simd_eq(i32x4::splat(5) * b), Mask::from_array([false, true, false, false])); + assert_eq!(a.simd_ne(i32x4::splat(5) * b), Mask::from_array([true, false, true, true])); + assert_eq!(a.simd_le(i32x4::splat(5) * b), Mask::from_array([false, true, true, false])); + assert_eq!(a.simd_lt(i32x4::splat(5) * b), Mask::from_array([false, false, true, false])); + assert_eq!(a.simd_ge(i32x4::splat(5) * b), Mask::from_array([true, true, false, true])); + assert_eq!(a.simd_gt(i32x4::splat(5) * b), Mask::from_array([true, false, false, true])); + + assert_eq!(a.reduce_sum(), 40); + assert_eq!(b.reduce_sum(), 2); + assert_eq!(a.reduce_product(), 100 * 100); + assert_eq!(b.reduce_product(), -24); + assert_eq!(a.reduce_max(), 10); + assert_eq!(b.reduce_max(), 3); + assert_eq!(a.reduce_min(), 10); + assert_eq!(b.reduce_min(), -4); + + assert_eq!(a.reduce_and(), 10); + assert_eq!(b.reduce_and(), 0); + assert_eq!(a.reduce_or(), 10); + assert_eq!(b.reduce_or(), -1); + assert_eq!(a.reduce_xor(), 0); + assert_eq!(b.reduce_xor(), -4); +} + +fn simd_mask() { + let intmask = Mask::from_int(i32x4::from_array([0, -1, 0, 0])); + assert_eq!(intmask, Mask::from_array([false, true, false, false])); + assert_eq!(intmask.to_array(), [false, true, false, false]); + + let values = [ + true, false, false, true, false, false, true, false, true, true, false, false, false, true, + false, true, + ]; + let mask = Mask::::from_array(values); + let bitmask = mask.to_bitmask(); + assert_eq!(bitmask, 0b1010001101001001); + assert_eq!(Mask::::from_bitmask(bitmask), mask); + + let values = [false, false, false, true]; + let mask = Mask::::from_array(values); + let bitmask = mask.to_bitmask(); + assert_eq!(bitmask, 0b1000); + assert_eq!(Mask::::from_bitmask(bitmask), mask); +} + +fn simd_cast() { + // between integer types + assert_eq!(i32x4::from_array([1, 2, 3, -4]), i16x4::from_array([1, 2, 3, -4]).cast()); + assert_eq!(i16x4::from_array([1, 2, 3, -4]), i32x4::from_array([1, 2, 3, -4]).cast()); + assert_eq!(i32x4::from_array([1, -1, 3, 4]), u64x4::from_array([1, u64::MAX, 3, 4]).cast()); + + // float -> int + assert_eq!( + i8x4::from_array([127, -128, 127, -128]), + f32x4::from_array([127.99, -128.99, 999.0, -999.0]).cast() + ); + assert_eq!( + i32x4::from_array([0, 1, -1, 2147483520]), + f32x4::from_array([ + -0.0, + /*0x1.19999ap+0*/ f32::from_bits(0x3f8ccccd), + /*-0x1.19999ap+0*/ f32::from_bits(0xbf8ccccd), + 2147483520.0 + ]) + .cast() + ); + assert_eq!( + i32x8::from_array([i32::MAX, i32::MIN, i32::MAX, i32::MIN, i32::MAX, i32::MIN, 0, 0]), + f32x8::from_array([ + 2147483648.0f32, + -2147483904.0f32, + f32::MAX, + f32::MIN, + f32::INFINITY, + f32::NEG_INFINITY, + f32::NAN, + -f32::NAN, + ]) + .cast() + ); + + // int -> float + assert_eq!( + f32x4::from_array([ + -2147483648.0, + /*0x1.26580cp+30*/ f32::from_bits(0x4e932c06), + 16777220.0, + -16777220.0, + ]), + i32x4::from_array([-2147483647i32, 1234567890i32, 16777219i32, -16777219i32]).cast() + ); + + // float -> float + assert_eq!( + f32x4::from_array([f32::INFINITY, f32::INFINITY, f32::NEG_INFINITY, f32::NEG_INFINITY]), + f64x4::from_array([f64::MAX, f64::INFINITY, f64::MIN, f64::NEG_INFINITY]).cast() + ); + + // unchecked casts + unsafe { + assert_eq!( + i32x4::from_array([0, 1, -1, 2147483520]), + f32x4::from_array([ + -0.0, + /*0x1.19999ap+0*/ f32::from_bits(0x3f8ccccd), + /*-0x1.19999ap+0*/ f32::from_bits(0xbf8ccccd), + 2147483520.0 + ]) + .to_int_unchecked() + ); + assert_eq!( + u64x4::from_array([0, 10000000000000000, u64::MAX - 2047, 9223372036854775808]), + f64x4::from_array([ + -0.99999999999, + 1e16, + (u64::MAX - 1024) as f64, + 9223372036854775808.0 + ]) + .to_int_unchecked() + ); + } +} + +fn simd_swizzle() { + use Which::*; + + let a = f32x4::splat(10.0); + let b = f32x4::from_array([1.0, 2.0, 3.0, -4.0]); + + assert_eq!(simd_swizzle!(b, [3, 0, 0, 2]), f32x4::from_array([-4.0, 1.0, 1.0, 3.0])); + assert_eq!(simd_swizzle!(b, [1, 2]), f32x2::from_array([2.0, 3.0])); + assert_eq!(simd_swizzle!(b, a, [First(3), Second(0)]), f32x2::from_array([-4.0, 10.0])); +} + +fn simd_gather_scatter() { + let mut vec: Vec = vec![10, 11, 12, 13, 14, 15, 16, 17, 18]; + let idxs = Simd::from_array([9, 3, 0, 17]); + let result = Simd::gather_or_default(&vec, idxs); // Note the lane that is out-of-bounds. + assert_eq!(result, Simd::from_array([0, 13, 10, 0])); + + let idxs = Simd::from_array([9, 3, 0, 0]); + Simd::from_array([-27, 82, -41, 124]).scatter(&mut vec, idxs); + assert_eq!(vec, vec![124, 11, 12, 82, 14, 15, 16, 17, 18]); +} + +fn simd_round() { + assert_eq!( + f32x4::from_array([0.9, 1.001, 2.0, -4.5]).ceil(), + f32x4::from_array([1.0, 2.0, 2.0, -4.0]) + ); + assert_eq!( + f32x4::from_array([0.9, 1.001, 2.0, -4.5]).floor(), + f32x4::from_array([0.0, 1.0, 2.0, -5.0]) + ); + assert_eq!( + f32x4::from_array([0.9, 1.001, 2.0, -4.5]).round(), + f32x4::from_array([1.0, 1.0, 2.0, -5.0]) + ); + assert_eq!( + f32x4::from_array([0.9, 1.001, 2.0, -4.5]).trunc(), + f32x4::from_array([0.0, 1.0, 2.0, -4.0]) + ); + + assert_eq!( + f64x4::from_array([0.9, 1.001, 2.0, -4.5]).ceil(), + f64x4::from_array([1.0, 2.0, 2.0, -4.0]) + ); + assert_eq!( + f64x4::from_array([0.9, 1.001, 2.0, -4.5]).floor(), + f64x4::from_array([0.0, 1.0, 2.0, -5.0]) + ); + assert_eq!( + f64x4::from_array([0.9, 1.001, 2.0, -4.5]).round(), + f64x4::from_array([1.0, 1.0, 2.0, -5.0]) + ); + assert_eq!( + f64x4::from_array([0.9, 1.001, 2.0, -4.5]).trunc(), + f64x4::from_array([0.0, 1.0, 2.0, -4.0]) + ); +} + +fn simd_intrinsics() { + extern "platform-intrinsic" { + fn simd_eq(x: T, y: T) -> U; + fn simd_reduce_any(x: T) -> bool; + fn simd_reduce_all(x: T) -> bool; + fn simd_select(m: M, yes: T, no: T) -> T; + } + unsafe { + // Make sure simd_eq returns all-1 for `true` + let a = i32x4::splat(10); + let b = i32x4::from_array([1, 2, 10, 4]); + let c: i32x4 = simd_eq(a, b); + assert_eq!(c, i32x4::from_array([0, 0, -1, 0])); + + assert!(!simd_reduce_any(i32x4::splat(0))); + assert!(simd_reduce_any(i32x4::splat(-1))); + assert!(simd_reduce_any(i32x2::from_array([0, -1]))); + assert!(!simd_reduce_all(i32x4::splat(0))); + assert!(simd_reduce_all(i32x4::splat(-1))); + assert!(!simd_reduce_all(i32x2::from_array([0, -1]))); + + assert_eq!( + simd_select(i8x4::from_array([0, -1, -1, 0]), a, b), + i32x4::from_array([1, 10, 10, 4]) + ); + assert_eq!( + simd_select(i8x4::from_array([0, -1, -1, 0]), b, a), + i32x4::from_array([10, 2, 10, 10]) + ); + } +} + +fn main() { + simd_mask(); + simd_ops_f32(); + simd_ops_f64(); + simd_ops_i32(); + simd_cast(); + simd_swizzle(); + simd_gather_scatter(); + simd_round(); + simd_intrinsics(); +} diff --git a/src/tools/miri/tests/pass/products.rs b/src/tools/miri/tests/pass/products.rs new file mode 100644 index 0000000000000..767d756e5ae18 --- /dev/null +++ b/src/tools/miri/tests/pass/products.rs @@ -0,0 +1,35 @@ +fn tuple() -> (i16,) { + (1,) +} + +fn tuple_2() -> (i16, i16) { + (1, 2) +} + +fn tuple_5() -> (i16, i16, i16, i16, i16) { + (1, 2, 3, 4, 5) +} + +#[derive(Debug, PartialEq)] +struct Pair { + x: i8, + y: i8, +} + +fn pair() -> Pair { + Pair { x: 10, y: 20 } +} + +fn field_access() -> (i8, i8) { + let mut p = Pair { x: 10, y: 20 }; + p.x += 5; + (p.x, p.y) +} + +fn main() { + assert_eq!(tuple(), (1,)); + assert_eq!(tuple_2(), (1, 2)); + assert_eq!(tuple_5(), (1, 2, 3, 4, 5)); + assert_eq!(pair(), Pair { x: 10, y: 20 }); + assert_eq!(field_access(), (15, 20)); +} diff --git a/src/tools/miri/tests/pass/ptr_int_casts.rs b/src/tools/miri/tests/pass/ptr_int_casts.rs new file mode 100644 index 0000000000000..3044ac092b7d4 --- /dev/null +++ b/src/tools/miri/tests/pass/ptr_int_casts.rs @@ -0,0 +1,73 @@ +//@compile-flags: -Zmiri-permissive-provenance +use std::mem; +use std::ptr; + +fn eq_ref(x: &T, y: &T) -> bool { + x as *const _ == y as *const _ +} + +fn f() -> i32 { + 42 +} + +fn ptr_int_casts() { + // int-ptr-int + assert_eq!(1 as *const i32 as usize, 1); + assert_eq!((1 as *const i32).wrapping_offset(4) as usize, 1 + 4 * 4); + + // negative overflowing wrapping_offset (going through memory because + // this used to trigger an ICE on 32bit) + let val = &mut ptr::null(); + *val = (1 as *const u8).wrapping_offset(-4); + assert_eq!(*val as usize, usize::MAX - 2); + + // ptr-int-ptr + { + let x = 13; + let mut y = &x as &_ as *const _ as usize; + y += 13; + y -= 13; + let y = y as *const _; + assert!(eq_ref(&x, unsafe { &*y })); + } + + // fnptr-int-fnptr + { + let x: fn() -> i32 = f; + let y: *mut u8 = unsafe { mem::transmute(x as fn() -> i32) }; + let mut y = y as usize; + y += 13; + y -= 13; + let x: fn() -> i32 = unsafe { mem::transmute(y as *mut u8) }; + assert_eq!(x(), 42); + } + + // involving types other than usize + assert_eq!((-1i32) as usize as *const i32 as usize, (-1i32) as usize); +} + +fn ptr_int_ops() { + let v = [1i16, 2]; + let x = &v[1] as *const i16 as usize; + // arithmetic + let _y = x + 4; + let _y = 4 + x; + let _y = x - 2; + // bit-operations, covered by alignment + assert_eq!(x & 1, 0); + assert_eq!(x & 0, 0); + assert_eq!(1 & (x + 1), 1); + let _y = !1 & x; + let _y = !0 & x; + let _y = x & !1; + // remainder, covered by alignment + assert_eq!(x % 2, 0); + assert_eq!((x + 1) % 2, 1); + // remainder with 1 is always 0 + assert_eq!(x % 1, 0); +} + +fn main() { + ptr_int_casts(); + ptr_int_ops(); +} diff --git a/src/tools/miri/tests/pass/ptr_int_from_exposed.rs b/src/tools/miri/tests/pass/ptr_int_from_exposed.rs new file mode 100644 index 0000000000000..ef7ff34d26b23 --- /dev/null +++ b/src/tools/miri/tests/pass/ptr_int_from_exposed.rs @@ -0,0 +1,62 @@ +//@compile-flags: -Zmiri-permissive-provenance +#![feature(strict_provenance)] + +use std::ptr; + +/// Ensure we can expose the address of a pointer that is out-of-bounds +fn ptr_roundtrip_out_of_bounds() { + let x: i32 = 3; + let x_ptr = &x as *const i32; + + let x_usize = x_ptr.wrapping_offset(128).expose_addr(); + + let ptr = ptr::from_exposed_addr::(x_usize).wrapping_offset(-128); + assert_eq!(unsafe { *ptr }, 3); +} + +/// Ensure that we can move between allocations after casting back to a ptr +fn ptr_roundtrip_confusion() { + let x: i32 = 0; + let y: i32 = 1; + + let x_ptr = &x as *const i32; + let y_ptr = &y as *const i32; + + let x_usize = x_ptr.expose_addr(); + let y_usize = y_ptr.expose_addr(); + + let ptr = ptr::from_exposed_addr::(y_usize); + let ptr = ptr.with_addr(x_usize); + assert_eq!(unsafe { *ptr }, 0); +} + +/// Ensure we can cast back a different integer than the one we got when exposing. +fn ptr_roundtrip_imperfect() { + let x: u8 = 3; + let x_ptr = &x as *const u8; + + let x_usize = x_ptr.expose_addr() + 128; + + let ptr = ptr::from_exposed_addr::(x_usize).wrapping_offset(-128); + assert_eq!(unsafe { *ptr }, 3); +} + +/// Ensure that we can roundtrip through a pointer with an address of 0 +fn ptr_roundtrip_null() { + let x = &42; + let x_ptr = x as *const i32; + let x_null_ptr = x_ptr.with_addr(0); // addr 0, but still the provenance of x + let null = x_null_ptr.expose_addr(); + assert_eq!(null, 0); + + let x_null_ptr_copy = ptr::from_exposed_addr::(null); // just a roundtrip, so has provenance of x (angelically) + let x_ptr_copy = x_null_ptr_copy.with_addr(x_ptr.addr()); // addr of x and provenance of x + assert_eq!(unsafe { *x_ptr_copy }, 42); +} + +fn main() { + ptr_roundtrip_out_of_bounds(); + ptr_roundtrip_confusion(); + ptr_roundtrip_imperfect(); + ptr_roundtrip_null(); +} diff --git a/src/tools/miri/tests/pass/ptr_int_transmute.rs b/src/tools/miri/tests/pass/ptr_int_transmute.rs new file mode 100644 index 0000000000000..ba50480c5399a --- /dev/null +++ b/src/tools/miri/tests/pass/ptr_int_transmute.rs @@ -0,0 +1,22 @@ +// Test what happens when we read parts of a pointer. +// Related to . +fn ptr_partial_read() { + let x = 13; + let y = &x; + let z = &y as *const &i32 as *const u8; + + // This just strips provenance, but should work fine otherwise. + let _val = unsafe { *z }; +} + +fn transmute_strip_provenance() { + let r = &mut 42; + let addr = r as *mut _ as usize; + let i: usize = unsafe { std::mem::transmute(r) }; + assert_eq!(i, addr); +} + +fn main() { + ptr_partial_read(); + transmute_strip_provenance(); +} diff --git a/src/tools/miri/tests/pass/ptr_offset.rs b/src/tools/miri/tests/pass/ptr_offset.rs new file mode 100644 index 0000000000000..95eac8522fb40 --- /dev/null +++ b/src/tools/miri/tests/pass/ptr_offset.rs @@ -0,0 +1,92 @@ +//@compile-flags: -Zmiri-permissive-provenance +#![feature(ptr_sub_ptr)] +use std::{mem, ptr}; + +fn main() { + smoke(); + test_offset_from(); + test_vec_into_iter(); + ptr_arith_offset(); + ptr_arith_offset_overflow(); + ptr_offset(); +} + +fn smoke() { + // Smoke-test various offsetting operations. + let ptr = &5; + let ptr = ptr as *const i32; + let _val = ptr.wrapping_offset(0); + let _val = unsafe { ptr.offset(0) }; + let _val = ptr.wrapping_add(0); + let _val = unsafe { ptr.add(0) }; + let _val = ptr.wrapping_sub(0); + let _val = unsafe { ptr.sub(0) }; + let _val = unsafe { ptr.offset_from(ptr) }; + let _val = unsafe { ptr.sub_ptr(ptr) }; +} + +fn test_offset_from() { + unsafe { + let buf = [0u32; 4]; + + let x = buf.as_ptr() as *const u8; + let y = x.offset(12); + + assert_eq!(y.offset_from(x), 12); + assert_eq!(y.sub_ptr(x), 12); + assert_eq!(x.offset_from(y), -12); + assert_eq!((y as *const u32).offset_from(x as *const u32), 12 / 4); + assert_eq!((x as *const u32).offset_from(y as *const u32), -12 / 4); + + let x = (((x as usize) * 2) / 2) as *const u8; + assert_eq!(y.offset_from(x), 12); + assert_eq!(y.sub_ptr(x), 12); + assert_eq!(x.offset_from(y), -12); + } +} + +// This also internally uses offset_from. +fn test_vec_into_iter() { + let v = Vec::::new(); + let i = v.into_iter(); + i.size_hint(); +} + +fn ptr_arith_offset() { + let v = [1i16, 2]; + let x = &v as *const [i16] as *const i16; + let x = x.wrapping_offset(1); + assert_eq!(unsafe { *x }, 2); +} + +fn ptr_arith_offset_overflow() { + let v = [1i16, 2]; + let x = &mut ptr::null(); // going through memory as there are more sanity checks along that path + *x = v.as_ptr().wrapping_offset(1); // ptr to the 2nd element + // Adding 2*isize::max and then 1 is like substracting 1 + *x = x.wrapping_offset(isize::MAX); + *x = x.wrapping_offset(isize::MAX); + *x = x.wrapping_offset(1); + assert_eq!(unsafe { **x }, 1); +} + +fn ptr_offset() { + fn f() -> i32 { + 42 + } + + let v = [1i16, 2]; + let x = &v as *const [i16; 2] as *const i16; + let x = unsafe { x.offset(1) }; + assert_eq!(unsafe { *x }, 2); + + // fn ptr offset + unsafe { + let p = f as fn() -> i32 as usize; + let x = (p as *mut u32).offset(0) as usize; + // *cast* to ptr, then transmute to fn ptr. + // (transmuting int to [fn]ptr causes trouble.) + let f: fn() -> i32 = mem::transmute(x as *const ()); + assert_eq!(f(), 42); + } +} diff --git a/src/tools/miri/tests/pass/ptr_raw.rs b/src/tools/miri/tests/pass/ptr_raw.rs new file mode 100644 index 0000000000000..3ba0fba9a9412 --- /dev/null +++ b/src/tools/miri/tests/pass/ptr_raw.rs @@ -0,0 +1,25 @@ +fn basic_raw() { + let mut x = 12; + let x = &mut x; + + assert_eq!(*x, 12); + + let raw = x as *mut i32; + unsafe { + *raw = 42; + } + + assert_eq!(*x, 42); + + let raw = x as *mut i32; + unsafe { + *raw = 12; + } + *x = 23; + + assert_eq!(*x, 23); +} + +fn main() { + basic_raw(); +} diff --git a/src/tools/miri/tests/pass/rc.rs b/src/tools/miri/tests/pass/rc.rs new file mode 100644 index 0000000000000..569dbc459a5b5 --- /dev/null +++ b/src/tools/miri/tests/pass/rc.rs @@ -0,0 +1,153 @@ +//@compile-flags: -Zmiri-strict-provenance +#![feature(new_uninit)] +#![feature(get_mut_unchecked)] + +use std::cell::{Cell, RefCell}; +use std::fmt::Debug; +use std::rc::{Rc, Weak}; +use std::sync::{Arc, Weak as ArcWeak}; + +fn rc_refcell() { + let r = Rc::new(RefCell::new(42)); + let r2 = r.clone(); + *r.borrow_mut() += 10; + let x = *r2.borrow(); + assert_eq!(x, 52); +} + +fn rc_cell() { + let r = Rc::new(Cell::new(42)); + let r2 = r.clone(); + let x = r.get(); + r2.set(x + x); + assert_eq!(r.get(), 84); +} + +fn rc_refcell2() { + let r = Rc::new(RefCell::new(42)); + let r2 = r.clone(); + *r.borrow_mut() += 10; + let x = r2.borrow(); + let r3 = r.clone(); + let y = r3.borrow(); + assert_eq!((*x + *y) / 2, 52); +} + +fn rc_raw() { + let r = Rc::new(0); + let r2 = Rc::into_raw(r.clone()); + let r2 = unsafe { Rc::from_raw(r2) }; + assert!(Rc::ptr_eq(&r, &r2)); + drop(r); + assert!(Rc::try_unwrap(r2).is_ok()); +} + +fn arc() { + fn test() -> Arc { + let a = Arc::new(42); + a + } + assert_eq!(*test(), 42); + + let raw = ArcWeak::into_raw(ArcWeak::::new()); + drop(unsafe { ArcWeak::from_raw(raw) }); +} + +// Make sure this Rc doesn't fall apart when touched +fn check_unique_rc(mut r: Rc) { + let r2 = r.clone(); + assert!(Rc::get_mut(&mut r).is_none()); + drop(r2); + assert!(Rc::get_mut(&mut r).is_some()); +} + +fn rc_from() { + check_unique_rc::<[_]>(Rc::from(&[1, 2, 3] as &[_])); + check_unique_rc::<[_]>(Rc::from(vec![1, 2, 3])); + check_unique_rc::<[_]>(Rc::from(Box::new([1, 2, 3]) as Box<[_]>)); + check_unique_rc::(Rc::from("Hello, World!")); +} + +fn rc_fat_ptr_eq() { + let p = Rc::new(1) as Rc; + let a: *const dyn Debug = &*p; + let r = Rc::into_raw(p); + assert!(a == r); + drop(unsafe { Rc::from_raw(r) }); +} + +/// Taken from the `Weak::into_raw` doctest. +fn weak_into_raw() { + let strong = Rc::new(42); + let weak = Rc::downgrade(&strong); + let raw = Weak::into_raw(weak); + + assert_eq!(1, Rc::weak_count(&strong)); + assert_eq!(42, unsafe { *raw }); + + drop(unsafe { Weak::from_raw(raw) }); + assert_eq!(0, Rc::weak_count(&strong)); + + let raw = Weak::into_raw(Weak::::new()); + drop(unsafe { Weak::from_raw(raw) }); +} + +/// Taken from the `Weak::from_raw` doctest. +fn weak_from_raw() { + let strong = Rc::new(42); + + let raw_1 = Weak::into_raw(Rc::downgrade(&strong)); + let raw_2 = Weak::into_raw(Rc::downgrade(&strong)); + + assert_eq!(2, Rc::weak_count(&strong)); + + assert_eq!(42, *Weak::upgrade(&unsafe { Weak::from_raw(raw_1) }).unwrap()); + assert_eq!(1, Rc::weak_count(&strong)); + + drop(strong); + + // Decrement the last weak count. + assert!(Weak::upgrade(&unsafe { Weak::from_raw(raw_2) }).is_none()); +} + +fn rc_uninit() { + let mut five = Rc::>::new_uninit(); + let five = unsafe { + // Deferred initialization: + Rc::get_mut_unchecked(&mut five).as_mut_ptr().write(Box::new(5)); + five.assume_init() + }; + assert_eq!(**five, 5) +} + +fn rc_uninit_slice() { + let mut values = Rc::<[Box]>::new_uninit_slice(3); + + let values = unsafe { + // Deferred initialization: + Rc::get_mut_unchecked(&mut values)[0].as_mut_ptr().write(Box::new(0)); + Rc::get_mut_unchecked(&mut values)[1].as_mut_ptr().write(Box::new(1)); + Rc::get_mut_unchecked(&mut values)[2].as_mut_ptr().write(Box::new(2)); + + values.assume_init() + }; + + for (idx, i) in values.iter().enumerate() { + assert_eq!(idx, **i); + } +} + +fn main() { + rc_fat_ptr_eq(); + rc_refcell(); + rc_refcell2(); + rc_cell(); + rc_raw(); + rc_from(); + weak_into_raw(); + weak_from_raw(); + rc_uninit(); + rc_uninit_slice(); + + arc(); +} diff --git a/src/tools/miri/tests/pass/recursive_static.rs b/src/tools/miri/tests/pass/recursive_static.rs new file mode 100644 index 0000000000000..77f2902917a1c --- /dev/null +++ b/src/tools/miri/tests/pass/recursive_static.rs @@ -0,0 +1,9 @@ +struct S(&'static S); +static S1: S = S(&S2); +static S2: S = S(&S1); + +fn main() { + let p: *const S = S2.0; + let q: *const S = &S1; + assert_eq!(p, q); +} diff --git a/src/tools/miri/tests/pass/reentrant-println.rs b/src/tools/miri/tests/pass/reentrant-println.rs new file mode 100644 index 0000000000000..e73e82b8ec9ed --- /dev/null +++ b/src/tools/miri/tests/pass/reentrant-println.rs @@ -0,0 +1,17 @@ +use std::fmt::{Display, Error, Formatter}; + +// This test case exercises std::sys_common::remutex::ReentrantMutex +// by calling println!() from inside fmt. + +struct InterruptingCow; + +impl Display for InterruptingCow { + fn fmt(&self, _f: &mut Formatter<'_>) -> Result<(), Error> { + println!("Moo"); + Ok(()) + } +} + +fn main() { + println!("\"Knock knock\" \"Who's {} there?\"", InterruptingCow); +} diff --git a/src/tools/miri/tests/pass/reentrant-println.stdout b/src/tools/miri/tests/pass/reentrant-println.stdout new file mode 100644 index 0000000000000..8a57d32f84ca6 --- /dev/null +++ b/src/tools/miri/tests/pass/reentrant-println.stdout @@ -0,0 +1,2 @@ +"Knock knock" "Who's Moo + there?" diff --git a/src/tools/miri/tests/pass/regions-lifetime-nonfree-late-bound.rs b/src/tools/miri/tests/pass/regions-lifetime-nonfree-late-bound.rs new file mode 100644 index 0000000000000..c91ac36ed6b41 --- /dev/null +++ b/src/tools/miri/tests/pass/regions-lifetime-nonfree-late-bound.rs @@ -0,0 +1,40 @@ +// This is a regression test for the ICE from issue #10846. +// +// The original issue causing the ICE: the LUB-computations during +// type inference were encountering late-bound lifetimes, and +// asserting that such lifetimes should have already been substituted +// with a concrete lifetime. +// +// However, those encounters were occurring within the lexical scope +// of the binding for the late-bound lifetime; that is, the late-bound +// lifetimes were perfectly valid. The core problem was that the type +// folding code was over-zealously passing back all lifetimes when +// doing region-folding, when really all clients of the region-folding +// case only want to see *free* lifetime variables, not bound ones. + +#![feature(box_syntax)] + +pub fn main() { + fn explicit() { + fn test(_x: Option>) + where + F: FnMut(Box FnMut(&'a isize)>), + { + } + test(Some(box |_f: Box FnMut(&'a isize)>| {})); + } + + // The code below is shorthand for the code above (and more likely + // to represent what one encounters in practice). + fn implicit() { + fn test(_x: Option>) + where + F: FnMut(Box), + { + } + test(Some(box |_f: Box| {})); + } + + explicit(); + implicit(); +} diff --git a/src/tools/miri/tests/pass/rename_std.rs b/src/tools/miri/tests/pass/rename_std.rs new file mode 100644 index 0000000000000..7e82e53e6be85 --- /dev/null +++ b/src/tools/miri/tests/pass/rename_std.rs @@ -0,0 +1,5 @@ +#![no_std] + +extern crate std as foo; + +fn main() {} diff --git a/src/tools/miri/tests/pass/rfc1623.rs b/src/tools/miri/tests/pass/rfc1623.rs new file mode 100644 index 0000000000000..76e2c01e74505 --- /dev/null +++ b/src/tools/miri/tests/pass/rfc1623.rs @@ -0,0 +1,71 @@ +#![allow(dead_code)] // tons of unused statics here... + +// very simple test for a 'static static with default lifetime +static STATIC_STR: &str = "&'static str"; +const CONST_STR: &str = "&'static str"; + +// this should be the same as without default: +static EXPLICIT_STATIC_STR: &'static str = "&'static str"; +const EXPLICIT_CONST_STR: &'static str = "&'static str"; + +// a function that elides to an unbound lifetime for both in- and output +fn id_u8_slice(arg: &[u8]) -> &[u8] { + arg +} + +// one with a function, argument elided +static STATIC_SIMPLE_FN: &fn(&[u8]) -> &[u8] = &(id_u8_slice as fn(&[u8]) -> &[u8]); +const CONST_SIMPLE_FN: &fn(&[u8]) -> &[u8] = &(id_u8_slice as fn(&[u8]) -> &[u8]); + +// this should be the same as without elision +static STATIC_NON_ELIDED_FN: &for<'a> fn(&'a [u8]) -> &'a [u8] = + &(id_u8_slice as for<'a> fn(&'a [u8]) -> &'a [u8]); +const CONST_NON_ELIDED_FN: &for<'a> fn(&'a [u8]) -> &'a [u8] = + &(id_u8_slice as for<'a> fn(&'a [u8]) -> &'a [u8]); + +// another function that elides, each to a different unbound lifetime +fn multi_args(_a: &u8, _b: &u8, _c: &u8) {} + +static STATIC_MULTI_FN: &fn(&u8, &u8, &u8) = &(multi_args as fn(&u8, &u8, &u8)); +const CONST_MULTI_FN: &fn(&u8, &u8, &u8) = &(multi_args as fn(&u8, &u8, &u8)); + +struct Foo<'a> { + bools: &'a [bool], +} + +static STATIC_FOO: Foo = Foo { bools: &[true, false] }; +const CONST_FOO: Foo = Foo { bools: &[true, false] }; + +type Bar<'a> = Foo<'a>; + +static STATIC_BAR: Bar = Bar { bools: &[true, false] }; +const CONST_BAR: Bar = Bar { bools: &[true, false] }; + +type Baz<'a> = fn(&'a [u8]) -> Option; + +fn baz(e: &[u8]) -> Option { + e.first().map(|x| *x) +} + +static STATIC_BAZ: &Baz = &(baz as Baz); +const CONST_BAZ: &Baz = &(baz as Baz); + +static BYTES: &[u8] = &[1, 2, 3]; + +fn main() { + // make sure that the lifetime is actually elided (and not defaulted) + let x = &[1u8, 2, 3]; + STATIC_SIMPLE_FN(x); + CONST_SIMPLE_FN(x); + + STATIC_BAZ(BYTES); // neees static lifetime + CONST_BAZ(BYTES); + + // make sure this works with different lifetimes + let a = &1; + { + let b = &2; + let c = &3; + CONST_MULTI_FN(a, b, c); + } +} diff --git a/src/tools/miri/tests/pass/rust-lang-org.rs b/src/tools/miri/tests/pass/rust-lang-org.rs new file mode 100644 index 0000000000000..7ba68e6b239c0 --- /dev/null +++ b/src/tools/miri/tests/pass/rust-lang-org.rs @@ -0,0 +1,21 @@ +// This code is editable and runnable! +fn main() { + // A simple integer calculator: + // `+` or `-` means add or subtract by 1 + // `*` or `/` means multiply or divide by 2 + + let program = "+ + * - /"; + let mut accumulator = 0; + + for token in program.chars() { + match token { + '+' => accumulator += 1, + '-' => accumulator -= 1, + '*' => accumulator *= 2, + '/' => accumulator /= 2, + _ => { /* ignore everything else */ } + } + } + + assert_eq!(accumulator, 1); +} diff --git a/src/tools/miri/tests/pass/send-is-not-static-par-for.rs b/src/tools/miri/tests/pass/send-is-not-static-par-for.rs new file mode 100644 index 0000000000000..642f75ecc09bf --- /dev/null +++ b/src/tools/miri/tests/pass/send-is-not-static-par-for.rs @@ -0,0 +1,28 @@ +use std::sync::Mutex; + +fn par_for(iter: I, f: F) +where + I: Iterator, + I::Item: Send, + F: Fn(I::Item) + Sync, +{ + for item in iter { + f(item) + } +} + +fn sum(x: &[i32]) { + let sum_lengths = Mutex::new(0); + par_for(x.windows(4), |x| *sum_lengths.lock().unwrap() += x.len()); + + assert_eq!(*sum_lengths.lock().unwrap(), (x.len() - 3) * 4); +} + +fn main() { + let mut elements = [0; 20]; + + // iterators over references into this stack frame + par_for(elements.iter_mut().enumerate(), |(i, x)| *x = i as i32); + + sum(&elements) +} diff --git a/src/tools/miri/tests/pass/sendable-class.rs b/src/tools/miri/tests/pass/sendable-class.rs new file mode 100644 index 0000000000000..a05278f1855a2 --- /dev/null +++ b/src/tools/miri/tests/pass/sendable-class.rs @@ -0,0 +1,19 @@ +// Test that a class with only sendable fields can be sent + +use std::sync::mpsc::channel; + +#[allow(dead_code)] +struct Foo { + i: isize, + j: char, +} + +fn foo(i: isize, j: char) -> Foo { + Foo { i: i, j: j } +} + +pub fn main() { + let (tx, rx) = channel(); + tx.send(foo(42, 'c')).unwrap(); + let _val = rx; +} diff --git a/src/tools/miri/tests/pass/shims/env/args.rs b/src/tools/miri/tests/pass/shims/env/args.rs new file mode 100644 index 0000000000000..0116dce4992dc --- /dev/null +++ b/src/tools/miri/tests/pass/shims/env/args.rs @@ -0,0 +1,5 @@ +fn main() { + for arg in std::env::args() { + println!("{}", arg); + } +} diff --git a/src/tools/miri/tests/pass/shims/env/args.stdout b/src/tools/miri/tests/pass/shims/env/args.stdout new file mode 100644 index 0000000000000..9564f5a1aa056 --- /dev/null +++ b/src/tools/miri/tests/pass/shims/env/args.stdout @@ -0,0 +1 @@ +args diff --git a/src/tools/miri/tests/pass/shims/env/current_dir.rs b/src/tools/miri/tests/pass/shims/env/current_dir.rs new file mode 100644 index 0000000000000..069b462ab371a --- /dev/null +++ b/src/tools/miri/tests/pass/shims/env/current_dir.rs @@ -0,0 +1,18 @@ +//@compile-flags: -Zmiri-disable-isolation +use std::env; +use std::io::ErrorKind; + +fn main() { + // Test that `getcwd` is available + let cwd = env::current_dir().unwrap(); + // Test that changing dir to `..` actually sets the current directory to the parent of `cwd`. + // The only exception here is if `cwd` is the root directory, then changing directory must + // keep the current directory equal to `cwd`. + let parent = cwd.parent().unwrap_or(&cwd); + // Test that `chdir` is available + assert!(env::set_current_dir("..").is_ok()); + // Test that `..` goes to the parent directory + assert_eq!(env::current_dir().unwrap(), parent); + // Test that `chdir` to a non-existing directory returns a proper error + assert_eq!(env::set_current_dir("thisdoesnotexist").unwrap_err().kind(), ErrorKind::NotFound); +} diff --git a/src/tools/miri/tests/pass/shims/env/current_dir_with_isolation.rs b/src/tools/miri/tests/pass/shims/env/current_dir_with_isolation.rs new file mode 100644 index 0000000000000..9dbcfeae2d644 --- /dev/null +++ b/src/tools/miri/tests/pass/shims/env/current_dir_with_isolation.rs @@ -0,0 +1,21 @@ +//@compile-flags: -Zmiri-isolation-error=warn-nobacktrace +//@normalize-stderr-test: "(getcwd|GetCurrentDirectoryW)" -> "$$GETCWD" +//@normalize-stderr-test: "(chdir|SetCurrentDirectoryW)" -> "$$SETCWD" + +use std::env; +use std::io::ErrorKind; + +fn main() { + // Test that current dir operations return a proper error instead + // of stopping the machine in isolation mode + assert_eq!(env::current_dir().unwrap_err().kind(), ErrorKind::PermissionDenied); + for _i in 0..3 { + // Ensure we get no repeated warnings when doing this multiple times. + assert_eq!(env::current_dir().unwrap_err().kind(), ErrorKind::PermissionDenied); + } + + assert_eq!(env::set_current_dir("..").unwrap_err().kind(), ErrorKind::PermissionDenied); + for _i in 0..3 { + assert_eq!(env::set_current_dir("..").unwrap_err().kind(), ErrorKind::PermissionDenied); + } +} diff --git a/src/tools/miri/tests/pass/shims/env/current_dir_with_isolation.stderr b/src/tools/miri/tests/pass/shims/env/current_dir_with_isolation.stderr new file mode 100644 index 0000000000000..589ca65a1e47e --- /dev/null +++ b/src/tools/miri/tests/pass/shims/env/current_dir_with_isolation.stderr @@ -0,0 +1,4 @@ +warning: `$GETCWD` was made to return an error due to isolation + +warning: `$SETCWD` was made to return an error due to isolation + diff --git a/src/tools/miri/tests/pass/shims/env/current_exe.rs b/src/tools/miri/tests/pass/shims/env/current_exe.rs new file mode 100644 index 0000000000000..15ea6a52b7b6b --- /dev/null +++ b/src/tools/miri/tests/pass/shims/env/current_exe.rs @@ -0,0 +1,9 @@ +//@ignore-target-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; + +fn main() { + // The actual value we get is a bit odd: we get the Miri binary that interprets us. + env::current_exe().unwrap(); +} diff --git a/src/tools/miri/tests/pass/shims/env/home.rs b/src/tools/miri/tests/pass/shims/env/home.rs new file mode 100644 index 0000000000000..9eb9c3af569dd --- /dev/null +++ b/src/tools/miri/tests/pass/shims/env/home.rs @@ -0,0 +1,9 @@ +//@ignore-target-windows: home_dir is not supported on Windows +//@compile-flags: -Zmiri-disable-isolation +use std::env; + +fn main() { + env::remove_var("HOME"); // make sure we enter the interesting codepath + #[allow(deprecated)] + env::home_dir().unwrap(); +} diff --git a/src/tools/miri/tests/pass/shims/env/var-forward.rs b/src/tools/miri/tests/pass/shims/env/var-forward.rs new file mode 100644 index 0000000000000..da7730b00f089 --- /dev/null +++ b/src/tools/miri/tests/pass/shims/env/var-forward.rs @@ -0,0 +1,5 @@ +//@compile-flags: -Zmiri-env-forward=MIRI_ENV_VAR_TEST + +fn main() { + assert_eq!(std::env::var("MIRI_ENV_VAR_TEST"), Ok("0".to_owned())); +} diff --git a/src/tools/miri/tests/pass/shims/env/var-without-isolation.rs b/src/tools/miri/tests/pass/shims/env/var-without-isolation.rs new file mode 100644 index 0000000000000..3d7461eecfe88 --- /dev/null +++ b/src/tools/miri/tests/pass/shims/env/var-without-isolation.rs @@ -0,0 +1,5 @@ +//@compile-flags: -Zmiri-disable-isolation + +fn main() { + assert_eq!(std::env::var("MIRI_ENV_VAR_TEST"), Ok("0".to_owned())); +} diff --git a/src/tools/miri/tests/pass/shims/env/var.rs b/src/tools/miri/tests/pass/shims/env/var.rs new file mode 100644 index 0000000000000..23a3724ff7fd3 --- /dev/null +++ b/src/tools/miri/tests/pass/shims/env/var.rs @@ -0,0 +1,26 @@ +use std::env; + +fn main() { + // Test that miri environment is isolated when communication is disabled. + // (`MIRI_ENV_VAR_TEST` is set by the test harness.) + assert_eq!(env::var("MIRI_ENV_VAR_TEST"), Err(env::VarError::NotPresent)); + + // Test base state. + println!("{:#?}", env::vars().collect::>()); + assert_eq!(env::var("MIRI_TEST"), Err(env::VarError::NotPresent)); + + // Set the variable. + env::set_var("MIRI_TEST", "the answer"); + assert_eq!(env::var("MIRI_TEST"), Ok("the answer".to_owned())); + println!("{:#?}", env::vars().collect::>()); + + // Change the variable. + env::set_var("MIRI_TEST", "42"); + assert_eq!(env::var("MIRI_TEST"), Ok("42".to_owned())); + println!("{:#?}", env::vars().collect::>()); + + // Remove the variable. + env::remove_var("MIRI_TEST"); + assert_eq!(env::var("MIRI_TEST"), Err(env::VarError::NotPresent)); + println!("{:#?}", env::vars().collect::>()); +} diff --git a/src/tools/miri/tests/pass/shims/env/var.stdout b/src/tools/miri/tests/pass/shims/env/var.stdout new file mode 100644 index 0000000000000..9a8f979598ebc --- /dev/null +++ b/src/tools/miri/tests/pass/shims/env/var.stdout @@ -0,0 +1,14 @@ +[] +[ + ( + "MIRI_TEST", + "the answer", + ), +] +[ + ( + "MIRI_TEST", + "42", + ), +] +[] diff --git a/src/tools/miri/tests/pass/shims/exit.rs b/src/tools/miri/tests/pass/shims/exit.rs new file mode 100644 index 0000000000000..d93f0045377ef --- /dev/null +++ b/src/tools/miri/tests/pass/shims/exit.rs @@ -0,0 +1,3 @@ +fn main() { + std::process::exit(0) +} diff --git a/src/tools/miri/tests/pass/shims/sleep_long.rs b/src/tools/miri/tests/pass/shims/sleep_long.rs new file mode 100644 index 0000000000000..dd4a1843942c4 --- /dev/null +++ b/src/tools/miri/tests/pass/shims/sleep_long.rs @@ -0,0 +1,18 @@ +//@ignore-target-windows: no threads nor sleep on Windows +//@compile-flags: -Zmiri-ignore-leaks -Zmiri-disable-isolation +use std::sync::{Arc, Mutex}; +use std::thread; +use std::time::Duration; + +fn main() { + let finished = Arc::new(Mutex::new(false)); + let t_finished = finished.clone(); + thread::spawn(move || { + // Sleep very, very long. + thread::sleep(Duration::new(u64::MAX, 0)); + *t_finished.lock().unwrap() = true; + }); + thread::sleep(Duration::from_millis(100)); + assert_eq!(*finished.lock().unwrap(), false); + // Stopping the main thread will also kill the sleeper. +} diff --git a/src/tools/miri/tests/pass/shims/time-with-isolation.rs b/src/tools/miri/tests/pass/shims/time-with-isolation.rs new file mode 100644 index 0000000000000..b6444319b59b9 --- /dev/null +++ b/src/tools/miri/tests/pass/shims/time-with-isolation.rs @@ -0,0 +1,35 @@ +use std::time::{Duration, Instant}; + +fn test_sleep() { + // We sleep a *long* time here -- but the clock is virtual so the test should still pass quickly. + let before = Instant::now(); + std::thread::sleep(Duration::from_secs(3600)); + let after = Instant::now(); + assert!((after - before).as_secs() >= 3600); +} + +/// Ensure that time passes even if we don't sleep (but just work). +fn test_time_passes() { + // Check `Instant`. + let now1 = Instant::now(); + // Do some work to make time pass. + for _ in 0..10 { + drop(vec![42]); + } + let now2 = Instant::now(); + assert!(now2 > now1); + // Sanity-check the difference we got. + let diff = now2.duration_since(now1); + assert_eq!(now1 + diff, now2); + assert_eq!(now2 - diff, now1); + // The virtual clock is deterministic and I got 29us on a 64-bit Linux machine. However, this + // changes according to the platform so we use an interval to be safe. This should be updated + // if `NANOSECONDS_PER_BASIC_BLOCK` changes. + assert!(diff.as_micros() > 10); + assert!(diff.as_micros() < 40); +} + +fn main() { + test_time_passes(); + test_sleep(); +} diff --git a/src/tools/miri/tests/pass/shims/time.rs b/src/tools/miri/tests/pass/shims/time.rs new file mode 100644 index 0000000000000..23b5ab57efa02 --- /dev/null +++ b/src/tools/miri/tests/pass/shims/time.rs @@ -0,0 +1,52 @@ +//@compile-flags: -Zmiri-disable-isolation + +use std::time::{Duration, Instant, SystemTime}; + +fn duration_sanity(diff: Duration) { + // On my laptop, I observed times around 15-40ms. Add 10x lee-way both ways. + assert!(diff.as_millis() > 1); + assert!(diff.as_millis() < 500); +} + +fn test_sleep() { + let before = Instant::now(); + std::thread::sleep(Duration::from_millis(100)); + let after = Instant::now(); + assert!((after - before).as_millis() >= 100); +} + +fn main() { + // Check `SystemTime`. + let now1 = SystemTime::now(); + let seconds_since_epoch = now1.duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs(); + let years_since_epoch = seconds_since_epoch / 3600 / 24 / 365; + let year = 1970 + years_since_epoch; + assert!(2020 <= year && year < 2100); + // Do some work to make time pass. + for _ in 0..10 { + drop(vec![42]); + } + let now2 = SystemTime::now(); + assert!(now2 > now1); + // Sanity-check the difference we got. + let diff = now2.duration_since(now1).unwrap(); + assert_eq!(now1 + diff, now2); + assert_eq!(now2 - diff, now1); + duration_sanity(diff); + + // Check `Instant`. + let now1 = Instant::now(); + // Do some work to make time pass. + for _ in 0..10 { + drop(vec![42]); + } + let now2 = Instant::now(); + assert!(now2 > now1); + // Sanity-check the difference we got. + let diff = now2.duration_since(now1); + assert_eq!(now1 + diff, now2); + assert_eq!(now2 - diff, now1); + duration_sanity(diff); + + test_sleep(); +} diff --git a/src/tools/miri/tests/pass/simd-intrinsic-generic-elements.rs b/src/tools/miri/tests/pass/simd-intrinsic-generic-elements.rs new file mode 100644 index 0000000000000..5958357c8b7b1 --- /dev/null +++ b/src/tools/miri/tests/pass/simd-intrinsic-generic-elements.rs @@ -0,0 +1,24 @@ +#![feature(repr_simd, platform_intrinsics)] + +#[repr(simd)] +#[derive(Copy, Clone, Debug, PartialEq)] +#[allow(non_camel_case_types)] +struct i32x2(i32, i32); +#[repr(simd)] +#[derive(Copy, Clone, Debug, PartialEq)] +#[allow(non_camel_case_types)] +struct i32x4(i32, i32, i32, i32); +#[repr(simd)] +#[derive(Copy, Clone, Debug, PartialEq)] +#[allow(non_camel_case_types)] +struct i32x8(i32, i32, i32, i32, i32, i32, i32, i32); + +fn main() { + let _x2 = i32x2(20, 21); + let _x4 = i32x4(40, 41, 42, 43); + let _x8 = i32x8(80, 81, 82, 83, 84, 85, 86, 87); + + let _y2 = i32x2(120, 121); + let _y4 = i32x4(140, 141, 142, 143); + let _y8 = i32x8(180, 181, 182, 183, 184, 185, 186, 187); +} diff --git a/src/tools/miri/tests/pass/slices.rs b/src/tools/miri/tests/pass/slices.rs new file mode 100644 index 0000000000000..a56b97a5088fa --- /dev/null +++ b/src/tools/miri/tests/pass/slices.rs @@ -0,0 +1,274 @@ +//@compile-flags: -Zmiri-strict-provenance +#![feature(new_uninit)] +#![feature(slice_as_chunks)] +#![feature(slice_partition_dedup)] +#![feature(layout_for_ptr)] +#![feature(strict_provenance)] + +use std::ptr; +use std::slice; + +fn slice_of_zst() { + fn foo(v: &[T]) -> Option<&[T]> { + let mut it = v.iter(); + for _ in 0..5 { + it.next(); + } + Some(it.as_slice()) + } + + fn foo_mut(v: &mut [T]) -> Option<&mut [T]> { + let mut it = v.iter_mut(); + for _ in 0..5 { + it.next(); + } + Some(it.into_slice()) + } + + // In a slice of zero-size elements the pointer is meaningless. + // Ensure iteration still works even if the pointer is at the end of the address space. + let slice: &[()] = unsafe { slice::from_raw_parts(ptr::invalid(-5isize as usize), 10) }; + assert_eq!(slice.len(), 10); + assert_eq!(slice.iter().count(), 10); + + // .nth() on the iterator should also behave correctly + let mut it = slice.iter(); + assert!(it.nth(5).is_some()); + assert_eq!(it.count(), 4); + + // Converting Iter to a slice should never have a null pointer + assert!(foo(slice).is_some()); + + // Test mutable iterators as well + let slice: &mut [()] = + unsafe { slice::from_raw_parts_mut(ptr::invalid_mut(-5isize as usize), 10) }; + assert_eq!(slice.len(), 10); + assert_eq!(slice.iter_mut().count(), 10); + + { + let mut it = slice.iter_mut(); + assert!(it.nth(5).is_some()); + assert_eq!(it.count(), 4); + } + + assert!(foo_mut(slice).is_some()) +} + +fn test_iter_ref_consistency() { + use std::fmt::Debug; + + fn test(x: T) { + let v: &[T] = &[x, x, x]; + let v_ptrs: [*const T; 3] = match v { + [ref v1, ref v2, ref v3] => [v1 as *const _, v2 as *const _, v3 as *const _], + _ => unreachable!(), + }; + let len = v.len(); + + // nth(i) + for i in 0..len { + assert_eq!(&v[i] as *const _, v_ptrs[i]); // check the v_ptrs array, just to be sure + let nth = v.iter().nth(i).unwrap(); + assert_eq!(nth as *const _, v_ptrs[i]); + } + assert_eq!(v.iter().nth(len), None, "nth(len) should return None"); + + // stepping through with nth(0) + { + let mut it = v.iter(); + for i in 0..len { + let next = it.nth(0).unwrap(); + assert_eq!(next as *const _, v_ptrs[i]); + } + assert_eq!(it.nth(0), None); + } + + // next() + { + let mut it = v.iter(); + for i in 0..len { + let remaining = len - i; + assert_eq!(it.size_hint(), (remaining, Some(remaining))); + + let next = it.next().unwrap(); + assert_eq!(next as *const _, v_ptrs[i]); + } + assert_eq!(it.size_hint(), (0, Some(0))); + assert_eq!(it.next(), None, "The final call to next() should return None"); + } + + // next_back() + { + let mut it = v.iter(); + for i in 0..len { + let remaining = len - i; + assert_eq!(it.size_hint(), (remaining, Some(remaining))); + + let prev = it.next_back().unwrap(); + assert_eq!(prev as *const _, v_ptrs[remaining - 1]); + } + assert_eq!(it.size_hint(), (0, Some(0))); + assert_eq!(it.next_back(), None, "The final call to next_back() should return None"); + } + } + + fn test_mut(x: T) { + let v: &mut [T] = &mut [x, x, x]; + let v_ptrs: [*mut T; 3] = match v { + [ref v1, ref v2, ref v3] => + [v1 as *const _ as *mut _, v2 as *const _ as *mut _, v3 as *const _ as *mut _], + _ => unreachable!(), + }; + let len = v.len(); + + // nth(i) + for i in 0..len { + assert_eq!(&mut v[i] as *mut _, v_ptrs[i]); // check the v_ptrs array, just to be sure + let nth = v.iter_mut().nth(i).unwrap(); + assert_eq!(nth as *mut _, v_ptrs[i]); + } + assert_eq!(v.iter().nth(len), None, "nth(len) should return None"); + + // stepping through with nth(0) + { + let mut it = v.iter(); + for i in 0..len { + let next = it.nth(0).unwrap(); + assert_eq!(next as *const _, v_ptrs[i]); + } + assert_eq!(it.nth(0), None); + } + + // next() + { + let mut it = v.iter_mut(); + for i in 0..len { + let remaining = len - i; + assert_eq!(it.size_hint(), (remaining, Some(remaining))); + + let next = it.next().unwrap(); + assert_eq!(next as *mut _, v_ptrs[i]); + } + assert_eq!(it.size_hint(), (0, Some(0))); + assert_eq!(it.next(), None, "The final call to next() should return None"); + } + + // next_back() + { + let mut it = v.iter_mut(); + for i in 0..len { + let remaining = len - i; + assert_eq!(it.size_hint(), (remaining, Some(remaining))); + + let prev = it.next_back().unwrap(); + assert_eq!(prev as *mut _, v_ptrs[remaining - 1]); + } + assert_eq!(it.size_hint(), (0, Some(0))); + assert_eq!(it.next_back(), None, "The final call to next_back() should return None"); + } + } + + // Make sure iterators and slice patterns yield consistent addresses for various types, + // including ZSTs. + test(0u32); + test(()); + test([0u32; 0]); // ZST with alignment > 0 + test_mut(0u32); + test_mut(()); + test_mut([0u32; 0]); // ZST with alignment > 0 +} + +fn uninit_slice() { + let mut values = Box::<[Box]>::new_uninit_slice(3); + + let values = unsafe { + // Deferred initialization: + values[0].as_mut_ptr().write(Box::new(1)); + values[1].as_mut_ptr().write(Box::new(2)); + values[2].as_mut_ptr().write(Box::new(3)); + + values.assume_init() + }; + + assert_eq!(values.iter().map(|x| **x).collect::>(), vec![1, 2, 3]) +} + +/// Regression tests for slice methods in the Rust core library where raw pointers are obtained +/// from mutable references. +fn test_for_invalidated_pointers() { + let mut buffer = [0usize; 64]; + let len = buffer.len(); + + // These regression tests (indirectly) call every slice method which contains a `buffer.as_mut_ptr()`. + // `<[T]>::as_mut_ptr(&mut self)` takes a mutable reference (tagged Unique), which will invalidate all + // the other pointers that were previously derived from it according to the Stacked Borrows model. + // An example of where this could go wrong is a prior bug inside `<[T]>::copy_within`: + // + // unsafe { + // core::ptr::copy(self.as_ptr().add(src_start), self.as_mut_ptr().add(dest), count); + // } + // + // The arguments to `core::ptr::copy` are evaluated from left to right. `self.as_ptr()` creates + // an immutable reference (which is tagged as `SharedReadOnly` by Stacked Borrows) to the array + // and derives a valid `*const` pointer from it. When jumping to the next argument, + // `self.as_mut_ptr()` creates a mutable reference (tagged as `Unique`) to the array, which + // invalidates the existing `SharedReadOnly` reference and any pointers derived from it. + // The invalidated `*const` pointer (the first argument to `core::ptr::copy`) is then used + // after the fact when `core::ptr::copy` is called, which triggers undefined behavior. + + unsafe { + assert_eq!(0, *buffer.as_mut_ptr_range().start); + } + // Check that the pointer range is in-bounds, while we're at it + let range = buffer.as_mut_ptr_range(); + unsafe { + assert_eq!(*range.start, *range.end.sub(len)); + } + + buffer.reverse(); + + // Calls `fn as_chunks_unchecked_mut` internally (requires unstable `#![feature(slice_as_chunks)]`): + assert_eq!(2, buffer.as_chunks_mut::<32>().0.len()); + for chunk in buffer.as_chunks_mut::<32>().0 { + for elem in chunk { + *elem += 1; + } + } + + // Calls `fn split_at_mut_unchecked` internally: + let split_mut = buffer.split_at_mut(32); + assert_eq!(split_mut.0, split_mut.1); + + // Calls `fn partition_dedup_by` internally (requires unstable `#![feature(slice_partition_dedup)]`): + let partition_dedup = buffer.partition_dedup(); + assert_eq!(1, partition_dedup.0.len()); + partition_dedup.0[0] += 1; + for elem in partition_dedup.1 { + *elem += 1; + } + + buffer.rotate_left(8); + buffer.rotate_right(16); + + buffer.copy_from_slice(&[1usize; 64]); + buffer.swap_with_slice(&mut [2usize; 64]); + + assert_eq!(0, unsafe { buffer.align_to_mut::().1[1] }); + + buffer.copy_within(1.., 0); +} + +fn large_raw_slice() { + let size = isize::MAX as usize; + // Creating a raw slice of size isize::MAX and asking for its size is okay. + let s = std::ptr::slice_from_raw_parts(ptr::invalid::(1), size); + assert_eq!(size, unsafe { std::mem::size_of_val_raw(s) }); +} + +fn main() { + slice_of_zst(); + test_iter_ref_consistency(); + uninit_slice(); + test_for_invalidated_pointers(); + large_raw_slice(); +} diff --git a/src/tools/miri/tests/pass/small_enum_size_bug.rs b/src/tools/miri/tests/pass/small_enum_size_bug.rs new file mode 100644 index 0000000000000..bb2f597444e77 --- /dev/null +++ b/src/tools/miri/tests/pass/small_enum_size_bug.rs @@ -0,0 +1,13 @@ +#[allow(dead_code)] +enum E { + A = 1, + B = 2, + C = 3, +} + +fn main() { + let enone = None::; + if let Some(..) = enone { + panic!(); + } +} diff --git a/src/tools/miri/tests/pass/specialization.rs b/src/tools/miri/tests/pass/specialization.rs new file mode 100644 index 0000000000000..428dea073eb53 --- /dev/null +++ b/src/tools/miri/tests/pass/specialization.rs @@ -0,0 +1,26 @@ +#![allow(incomplete_features)] +#![feature(specialization)] + +trait IsUnit { + fn is_unit() -> bool; +} + +impl IsUnit for T { + default fn is_unit() -> bool { + false + } +} + +impl IsUnit for () { + fn is_unit() -> bool { + true + } +} + +fn specialization() -> (bool, bool) { + (i32::is_unit(), <()>::is_unit()) +} + +fn main() { + assert_eq!(specialization(), (false, true)); +} diff --git a/src/tools/miri/tests/pass/stacked-borrows/2phase.rs b/src/tools/miri/tests/pass/stacked-borrows/2phase.rs new file mode 100644 index 0000000000000..eb543d691e10b --- /dev/null +++ b/src/tools/miri/tests/pass/stacked-borrows/2phase.rs @@ -0,0 +1,87 @@ +trait S: Sized { + fn tpb(&mut self, _s: Self) {} +} + +impl S for i32 {} + +fn two_phase1() { + let mut x = 3; + x.tpb(x); +} + +fn two_phase2() { + let mut v = vec![]; + v.push(v.len()); +} + +fn two_phase3(b: bool) { + let mut x = &mut vec![]; + let mut y = vec![]; + x.push(( + { + if b { + x = &mut y; + } + 22 + }, + x.len(), + )); +} + +#[allow(unreachable_code)] +fn two_phase_raw() { + let x: &mut Vec = &mut vec![]; + x.push({ + // Unfortunately this does not trigger the problem of creating a + // raw ponter from a pointer that had a two-phase borrow derived from + // it because of the implicit &mut reborrow. + let raw = x as *mut _; + unsafe { + *raw = vec![1]; + } + return; + }); +} + +fn two_phase_overlapping1() { + let mut x = vec![]; + let p = &x; + x.push(p.len()); +} + +fn two_phase_overlapping2() { + use std::ops::AddAssign; + let mut x = 1; + let l = &x; + x.add_assign(x + *l); +} + +fn with_interior_mutability() { + use std::cell::Cell; + + trait Thing: Sized { + fn do_the_thing(&mut self, _s: i32) {} + } + + impl Thing for Cell {} + + let mut x = Cell::new(1); + let l = &x; + + x.do_the_thing({ + x.set(3); + l.set(4); + x.get() + l.get() + }); +} + +fn main() { + two_phase1(); + two_phase2(); + two_phase3(false); + two_phase3(true); + two_phase_raw(); + with_interior_mutability(); + two_phase_overlapping1(); + two_phase_overlapping2(); +} diff --git a/src/tools/miri/tests/pass/stacked-borrows/generators-self-referential.rs b/src/tools/miri/tests/pass/stacked-borrows/generators-self-referential.rs new file mode 100644 index 0000000000000..b71912882dd2a --- /dev/null +++ b/src/tools/miri/tests/pass/stacked-borrows/generators-self-referential.rs @@ -0,0 +1,34 @@ +// See https://github.com/rust-lang/unsafe-code-guidelines/issues/148: +// this fails when Stacked Borrows is strictly applied even to `!Unpin` types. +#![feature(generators, generator_trait)] + +use std::{ + ops::{Generator, GeneratorState}, + pin::Pin, +}; + +fn firstn() -> impl Generator { + static move || { + let mut num = 0; + let num = &mut num; + + yield *num; + *num += 1; // would fail here + + yield *num; + *num += 1; + + yield *num; + *num += 1; + } +} + +fn main() { + let mut generator_iterator = firstn(); + let mut pin = unsafe { Pin::new_unchecked(&mut generator_iterator) }; + let mut sum = 0; + while let GeneratorState::Yielded(x) = pin.as_mut().resume(()) { + sum += x; + } + assert_eq!(sum, 3); +} diff --git a/src/tools/miri/tests/pass/stacked-borrows/int-to-ptr.rs b/src/tools/miri/tests/pass/stacked-borrows/int-to-ptr.rs new file mode 100644 index 0000000000000..c3e30627a7ce3 --- /dev/null +++ b/src/tools/miri/tests/pass/stacked-borrows/int-to-ptr.rs @@ -0,0 +1,83 @@ +//@compile-flags: -Zmiri-permissive-provenance +#![feature(strict_provenance)] +use std::ptr; + +// Just to make sure that casting a ref to raw, to int and back to raw +// and only then using it works. This rules out ideas like "do escape-to-raw lazily"; +// after casting to int and back, we lost the tag that could have let us do that. +fn ref_raw_int_raw() { + let mut x = 3; + let xref = &mut x; + let xraw = xref as *mut i32 as usize as *mut i32; + assert_eq!(unsafe { *xraw }, 3); +} + +/// Ensure that we do not just pick the topmost possible item on int2ptr casts. +fn example(variant: bool) { + unsafe { + fn not_so_innocent(x: &mut u32) -> usize { + let x_raw4 = x as *mut u32; + x_raw4.expose_addr() + } + + let mut c = 42u32; + + let x_unique1 = &mut c; + // [..., Unique(1)] + + let x_raw2 = x_unique1 as *mut u32; + let x_raw2_addr = x_raw2.expose_addr(); + // [..., Unique(1), SharedRW(2)] + + let x_unique3 = &mut *x_raw2; + // [.., Unique(1), SharedRW(2), Unique(3)] + + assert_eq!(not_so_innocent(x_unique3), x_raw2_addr); + // [.., Unique(1), SharedRW(2), Unique(3), ..., SharedRW(4)] + + // Do an int2ptr cast. This can pick tag 2 or 4 (the two previously exposed tags). + // 4 is the "obvious" choice (topmost tag, what we used to do with untagged pointers). + // And indeed if `variant == true` it is the only possible choice. + // But if `variant == false` then 2 is the only possible choice! + let x_wildcard = ptr::from_exposed_addr_mut::(x_raw2_addr); + + if variant { + // If we picked 2, this will invalidate 3. + *x_wildcard = 10; + // Now we use 3. Only possible if above we picked 4. + *x_unique3 = 12; + } else { + // This invalidates tag 4. + *x_unique3 = 10; + // Now try to write with the "guessed" tag; it must be 2. + *x_wildcard = 12; + } + } +} + +fn test() { + unsafe { + let root = &mut 42; + let root_raw = root as *mut i32; + let addr1 = root_raw as usize; + let child = &mut *root_raw; + let child_raw = child as *mut i32; + let addr2 = child_raw as usize; + assert_eq!(addr1, addr2); + // First use child. + *(addr2 as *mut i32) -= 2; // picks child_raw + *child -= 2; + // Then use root. + *(addr1 as *mut i32) += 2; // picks root_raw + *root += 2; + // Value should be unchanged. + assert_eq!(*root, 42); + } +} + +fn main() { + ref_raw_int_raw(); + example(false); + example(true); + test(); +} diff --git a/src/tools/miri/tests/pass/stacked-borrows/interior_mutability.rs b/src/tools/miri/tests/pass/stacked-borrows/interior_mutability.rs new file mode 100644 index 0000000000000..c6373a7eaf135 --- /dev/null +++ b/src/tools/miri/tests/pass/stacked-borrows/interior_mutability.rs @@ -0,0 +1,175 @@ +//@compile-flags: -Zmiri-retag-fields +use std::cell::{Cell, Ref, RefCell, RefMut, UnsafeCell}; +use std::mem::{self, MaybeUninit}; + +fn main() { + aliasing_mut_and_shr(); + aliasing_frz_and_shr(); + into_interior_mutability(); + unsafe_cell_2phase(); + unsafe_cell_deallocate(); + unsafe_cell_invalidate(); + refcell_basic(); + ref_protector(); + ref_mut_protector(); + rust_issue_68303(); +} + +fn aliasing_mut_and_shr() { + fn inner(rc: &RefCell, aliasing: &mut i32) { + *aliasing += 4; + let _escape_to_raw = rc as *const _; + *aliasing += 4; + let _shr = &*rc; + *aliasing += 4; + // also turning this into a frozen ref now must work + let aliasing = &*aliasing; + let _val = *aliasing; + let _escape_to_raw = rc as *const _; // this must NOT unfreeze + let _val = *aliasing; + let _shr = &*rc; // this must NOT unfreeze + let _val = *aliasing; + } + + let rc = RefCell::new(23); + let mut bmut = rc.borrow_mut(); + inner(&rc, &mut *bmut); + drop(bmut); + assert_eq!(*rc.borrow(), 23 + 12); +} + +fn aliasing_frz_and_shr() { + fn inner(rc: &RefCell, aliasing: &i32) { + let _val = *aliasing; + let _escape_to_raw = rc as *const _; // this must NOT unfreeze + let _val = *aliasing; + let _shr = &*rc; // this must NOT unfreeze + let _val = *aliasing; + } + + let rc = RefCell::new(23); + let bshr = rc.borrow(); + inner(&rc, &*bshr); + assert_eq!(*rc.borrow(), 23); +} + +// Getting a pointer into a union with interior mutability used to be tricky +// business (https://github.com/rust-lang/miri/issues/615), but it should work +// now. +fn into_interior_mutability() { + let mut x: MaybeUninit<(Cell, u32)> = MaybeUninit::uninit(); + x.as_ptr(); + x.write((Cell::new(0), 1)); + let ptr = unsafe { x.assume_init_ref() }; + assert_eq!(ptr.1, 1); +} + +// Two-phase borrows of the pointer returned by UnsafeCell::get() should not +// invalidate aliases. +fn unsafe_cell_2phase() { + unsafe { + let x = &UnsafeCell::new(vec![]); + let x2 = &*x; + (*x.get()).push(0); + let _val = (*x2.get()).get(0); + } +} + +/// Make sure we can deallocate an UnsafeCell that was passed to an active fn call. +/// (This is the fix for https://github.com/rust-lang/rust/issues/55005.) +fn unsafe_cell_deallocate() { + fn f(x: &UnsafeCell) { + let b: Box = unsafe { Box::from_raw(x as *const _ as *mut i32) }; + drop(b) + } + + let b = Box::new(0i32); + f(unsafe { mem::transmute(Box::into_raw(b)) }); +} + +/// As a side-effect of the above, we also allow this -- at least for now. +fn unsafe_cell_invalidate() { + fn f(_x: &UnsafeCell, y: *mut i32) { + // Writing to y invalidates x, but that is okay. + unsafe { + *y += 1; + } + } + + let mut x = 0i32; + let raw1 = &mut x as *mut _; + let ref1 = unsafe { &mut *raw1 }; + let raw2 = ref1 as *mut _; + // Now the borrow stack is: raw1, ref2, raw2. + // So using raw1 invalidates raw2. + f(unsafe { mem::transmute(raw2) }, raw1); +} + +fn refcell_basic() { + let c = RefCell::new(42); + { + let s1 = c.borrow(); + let _x: i32 = *s1; + let s2 = c.borrow(); + let _x: i32 = *s1; + let _y: i32 = *s2; + let _x: i32 = *s1; + let _y: i32 = *s2; + } + { + let mut m = c.borrow_mut(); + let _z: i32 = *m; + { + let s: &i32 = &*m; + let _x = *s; + } + *m = 23; + let _z: i32 = *m; + } + { + let s1 = c.borrow(); + let _x: i32 = *s1; + let s2 = c.borrow(); + let _x: i32 = *s1; + let _y: i32 = *s2; + let _x: i32 = *s1; + let _y: i32 = *s2; + } +} + +// Adding a Stacked Borrows protector for `Ref` would break this +fn ref_protector() { + fn break_it(rc: &RefCell, r: Ref<'_, i32>) { + // `r` has a shared reference, it is passed in as argument and hence + // a protector is added that marks this memory as read-only for the entire + // duration of this function. + drop(r); + // *oops* here we can mutate that memory. + *rc.borrow_mut() = 2; + } + + let rc = RefCell::new(0); + break_it(&rc, rc.borrow()) +} + +fn ref_mut_protector() { + fn break_it(rc: &RefCell, r: RefMut<'_, i32>) { + // `r` has a shared reference, it is passed in as argument and hence + // a protector is added that marks this memory as inaccessible for the entire + // duration of this function + drop(r); + // *oops* here we can mutate that memory. + *rc.borrow_mut() = 2; + } + + let rc = RefCell::new(0); + break_it(&rc, rc.borrow_mut()) +} + +/// Make sure we do not have bad enum layout optimizations. +fn rust_issue_68303() { + let optional = Some(RefCell::new(false)); + let mut handle = optional.as_ref().unwrap().borrow_mut(); + assert!(optional.is_some()); + *handle = true; +} diff --git a/src/tools/miri/tests/pass/stacked-borrows/issue-miri-2389.rs b/src/tools/miri/tests/pass/stacked-borrows/issue-miri-2389.rs new file mode 100644 index 0000000000000..469122095e512 --- /dev/null +++ b/src/tools/miri/tests/pass/stacked-borrows/issue-miri-2389.rs @@ -0,0 +1,17 @@ +use std::cell::Cell; + +fn main() { + unsafe { + let root0 = Cell::new(42); + let wildcard = &root0 as *const Cell as usize as *const Cell; + // empty the stack to unknown (via SRW reborrow from wildcard) + let _ref0 = &*wildcard; + // Do a non-SRW reborrow from wildcard to start building up a stack again. + // Now new refs start being inserted at idx 0, pushing the unique_range up. + let _refn = &*&*&*&*&*(wildcard.cast::()); + // empty the stack again, but this time with unique_range.start sitting at some high index. + let _ref0 = &*wildcard; + // and do a read which tries to clear the uniques + wildcard.cast::().read(); + } +} diff --git a/src/tools/miri/tests/pass/stacked-borrows/issue-miri-2389.stderr b/src/tools/miri/tests/pass/stacked-borrows/issue-miri-2389.stderr new file mode 100644 index 0000000000000..f3ba052ae5130 --- /dev/null +++ b/src/tools/miri/tests/pass/stacked-borrows/issue-miri-2389.stderr @@ -0,0 +1,15 @@ +warning: integer-to-pointer cast + --> $DIR/issue-miri-2389.rs:LL:CC + | +LL | let wildcard = &root0 as *const Cell as usize as *const Cell; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ integer-to-pointer cast + | + = help: This program is using integer-to-pointer casts or (equivalently) `ptr::from_exposed_addr`, + = help: which means that Miri might miss pointer bugs in this program. + = help: See https://doc.rust-lang.org/nightly/std/ptr/fn.from_exposed_addr.html for more details on that operation. + = help: To ensure that Miri does not miss bugs in your program, use Strict Provenance APIs (https://doc.rust-lang.org/nightly/std/ptr/index.html#strict-provenance, https://crates.io/crates/sptr) instead. + = help: You can then pass the `-Zmiri-strict-provenance` flag to Miri, to ensure you are not relying on `from_exposed_addr` semantics. + = help: Alternatively, the `-Zmiri-permissive-provenance` flag disables this warning. + = note: BACKTRACE: + = note: inside `main` at $DIR/issue-miri-2389.rs:LL:CC + diff --git a/src/tools/miri/tests/pass/stacked-borrows/stacked-borrows.rs b/src/tools/miri/tests/pass/stacked-borrows/stacked-borrows.rs new file mode 100644 index 0000000000000..ef6eb346c17b1 --- /dev/null +++ b/src/tools/miri/tests/pass/stacked-borrows/stacked-borrows.rs @@ -0,0 +1,221 @@ +//@compile-flags: -Zmiri-retag-fields +#![feature(allocator_api)] +use std::ptr; + +// Test various stacked-borrows-related things. +fn main() { + read_does_not_invalidate1(); + read_does_not_invalidate2(); + mut_raw_then_mut_shr(); + mut_shr_then_mut_raw(); + mut_raw_mut(); + partially_invalidate_mut(); + drop_after_sharing(); + direct_mut_to_const_raw(); + two_raw(); + shr_and_raw(); + disjoint_mutable_subborrows(); + raw_ref_to_part(); + array_casts(); + mut_below_shr(); + wide_raw_ptr_in_tuple(); +} + +// Make sure that reading from an `&mut` does, like reborrowing to `&`, +// NOT invalidate other reborrows. +fn read_does_not_invalidate1() { + fn foo(x: &mut (i32, i32)) -> &i32 { + let xraw = x as *mut (i32, i32); + let ret = unsafe { &(*xraw).1 }; + let _val = x.1; // we just read, this does NOT invalidate the reborrows. + ret + } + assert_eq!(*foo(&mut (1, 2)), 2); +} +// Same as above, but this time we first create a raw, then read from `&mut` +// and then freeze from the raw. +fn read_does_not_invalidate2() { + fn foo(x: &mut (i32, i32)) -> &i32 { + let xraw = x as *mut (i32, i32); + let _val = x.1; // we just read, this does NOT invalidate the raw reborrow. + let ret = unsafe { &(*xraw).1 }; + ret + } + assert_eq!(*foo(&mut (1, 2)), 2); +} + +// Escape a mut to raw, then share the same mut and use the share, then the raw. +// That should work. +fn mut_raw_then_mut_shr() { + let mut x = 2; + let xref = &mut x; + let xraw = &mut *xref as *mut _; + let xshr = &*xref; + assert_eq!(*xshr, 2); + unsafe { + *xraw = 4; + } + assert_eq!(x, 4); +} + +// Create first a shared reference and then a raw pointer from a `&mut` +// should permit mutation through that raw pointer. +fn mut_shr_then_mut_raw() { + let xref = &mut 2; + let _xshr = &*xref; + let xraw = xref as *mut _; + unsafe { + *xraw = 3; + } + assert_eq!(*xref, 3); +} + +// Ensure that if we derive from a mut a raw, and then from that a mut, +// and then read through the original mut, that does not invalidate the raw. +// This shows that the read-exception for `&mut` applies even if the `Shr` item +// on the stack is not at the top. +fn mut_raw_mut() { + let mut x = 2; + { + let xref1 = &mut x; + let xraw = xref1 as *mut _; + let _xref2 = unsafe { &mut *xraw }; + let _val = *xref1; + unsafe { + *xraw = 4; + } + // we can now use both xraw and xref1, for reading + assert_eq!(*xref1, 4); + assert_eq!(unsafe { *xraw }, 4); + assert_eq!(*xref1, 4); + assert_eq!(unsafe { *xraw }, 4); + // we cannot use xref2; see `compile-fail/stacked-borows/illegal_read4.rs` + } + assert_eq!(x, 4); +} + +fn partially_invalidate_mut() { + let data = &mut (0u8, 0u8); + let reborrow = &mut *data as *mut (u8, u8); + let shard = unsafe { &mut (*reborrow).0 }; + data.1 += 1; // the deref overlaps with `shard`, but that is ok; the access does not overlap. + *shard += 1; // so we can still use `shard`. + assert_eq!(*data, (1, 1)); +} + +// Make sure that we can handle the situation where a loaction is frozen when being dropped. +fn drop_after_sharing() { + let x = String::from("hello!"); + let _len = x.len(); +} + +// Make sure that coercing &mut T to *const T produces a writeable pointer. +fn direct_mut_to_const_raw() { + // TODO: This is currently disabled, waiting on a decision on + /*let x = &mut 0; + let y: *const i32 = x; + unsafe { *(y as *mut i32) = 1; } + assert_eq!(*x, 1); + */ +} + +// Make sure that we can create two raw pointers from a mutable reference and use them both. +fn two_raw() { + unsafe { + let x = &mut 0; + let y1 = x as *mut _; + let y2 = x as *mut _; + *y1 += 2; + *y2 += 1; + } +} + +// Make sure that creating a *mut does not invalidate existing shared references. +fn shr_and_raw() { + unsafe { + use std::mem; + let x = &mut 0; + let y1: &i32 = mem::transmute(&*x); // launder lifetimes + let y2 = x as *mut _; + let _val = *y1; + *y2 += 1; + } +} + +fn disjoint_mutable_subborrows() { + struct Foo { + a: String, + b: Vec, + } + + unsafe fn borrow_field_a<'a>(this: *mut Foo) -> &'a mut String { + &mut (*this).a + } + + unsafe fn borrow_field_b<'a>(this: *mut Foo) -> &'a mut Vec { + &mut (*this).b + } + + let mut foo = Foo { a: "hello".into(), b: vec![0, 1, 2] }; + + let ptr = &mut foo as *mut Foo; + + let a = unsafe { borrow_field_a(ptr) }; + let b = unsafe { borrow_field_b(ptr) }; + b.push(4); + a.push_str(" world"); + eprintln!("{:?} {:?}", a, b); +} + +fn raw_ref_to_part() { + struct Part { + _lame: i32, + } + + #[repr(C)] + struct Whole { + part: Part, + extra: i32, + } + + let it = Box::new(Whole { part: Part { _lame: 0 }, extra: 42 }); + let whole = ptr::addr_of_mut!(*Box::leak(it)); + let part = unsafe { ptr::addr_of_mut!((*whole).part) }; + let typed = unsafe { &mut *(part as *mut Whole) }; + assert!(typed.extra == 42); + drop(unsafe { Box::from_raw(whole) }); +} + +/// When casting an array reference to a raw element ptr, that should cover the whole array. +fn array_casts() { + let mut x: [usize; 2] = [0, 0]; + let p = &mut x as *mut usize; + unsafe { + *p.add(1) = 1; + } + + let x: [usize; 2] = [0, 1]; + let p = &x as *const usize; + assert_eq!(unsafe { *p.add(1) }, 1); +} + +/// Transmuting &&i32 to &&mut i32 is fine. +fn mut_below_shr() { + let x = 0; + let y = &x; + let p = unsafe { core::mem::transmute::<&&i32, &&mut i32>(&y) }; + let r = &**p; + let _val = *r; +} + +fn wide_raw_ptr_in_tuple() { + let mut x: Box = Box::new("ouch"); + let r = &mut *x as *mut dyn std::any::Any; + // This triggers the visitor-based recursive retagging. It is *not* supposed to retag raw + // pointers, but then the visitor might recurse into the "fields" of a wide raw pointer and + // finds a reference (to a vtable) there that it wants to retag... and that would be Wrong. + let pair = (r, &0); + let r = unsafe { &mut *pair.0 }; + // Make sure the fn ptr part of the vtable is still fine. + r.type_id(); +} diff --git a/src/tools/miri/tests/pass/stacked-borrows/stacked-borrows.stderr b/src/tools/miri/tests/pass/stacked-borrows/stacked-borrows.stderr new file mode 100644 index 0000000000000..8ee4e25dbef84 --- /dev/null +++ b/src/tools/miri/tests/pass/stacked-borrows/stacked-borrows.stderr @@ -0,0 +1 @@ +"hello world" [0, 1, 2, 4] diff --git a/src/tools/miri/tests/pass/stacked-borrows/zst-field-retagging-terminates.rs b/src/tools/miri/tests/pass/stacked-borrows/zst-field-retagging-terminates.rs new file mode 100644 index 0000000000000..ce3c8b7d5f1a1 --- /dev/null +++ b/src/tools/miri/tests/pass/stacked-borrows/zst-field-retagging-terminates.rs @@ -0,0 +1,6 @@ +//@compile-flags: -Zmiri-retag-fields +// Checks that the test does not run forever (which relies on a fast path). +fn main() { + let array = [(); usize::MAX]; + drop(array); // Pass the array to a function, retagging its fields +} diff --git a/src/tools/miri/tests/pass/start.rs b/src/tools/miri/tests/pass/start.rs new file mode 100644 index 0000000000000..f25d62fa8c335 --- /dev/null +++ b/src/tools/miri/tests/pass/start.rs @@ -0,0 +1,8 @@ +#![feature(start)] + +#[start] +fn start(_: isize, _: *const *const u8) -> isize { + println!("Hello from start!"); + + 0 +} diff --git a/src/tools/miri/tests/pass/start.stdout b/src/tools/miri/tests/pass/start.stdout new file mode 100644 index 0000000000000..d7f627d237c3e --- /dev/null +++ b/src/tools/miri/tests/pass/start.stdout @@ -0,0 +1 @@ +Hello from start! diff --git a/src/tools/miri/tests/pass/static_memory_modification.rs b/src/tools/miri/tests/pass/static_memory_modification.rs new file mode 100644 index 0000000000000..84a524b1ed163 --- /dev/null +++ b/src/tools/miri/tests/pass/static_memory_modification.rs @@ -0,0 +1,14 @@ +use std::sync::atomic::{AtomicUsize, Ordering}; + +static mut X: usize = 5; +static Y: AtomicUsize = AtomicUsize::new(5); + +fn main() { + unsafe { + X = 6; + assert_eq!(X, 6); + } + + Y.store(6, Ordering::Relaxed); + assert_eq!(Y.load(Ordering::Relaxed), 6); +} diff --git a/src/tools/miri/tests/pass/static_mut.rs b/src/tools/miri/tests/pass/static_mut.rs new file mode 100644 index 0000000000000..218b02525bd55 --- /dev/null +++ b/src/tools/miri/tests/pass/static_mut.rs @@ -0,0 +1,16 @@ +static mut FOO: i32 = 42; +static BAR: Foo = Foo(unsafe { &FOO as *const _ }); + +#[allow(dead_code)] +struct Foo(*const i32); + +unsafe impl Sync for Foo {} + +fn main() { + unsafe { + assert_eq!(*BAR.0, 42); + FOO = 5; + assert_eq!(FOO, 5); + assert_eq!(*BAR.0, 5); + } +} diff --git a/src/tools/miri/tests/pass/strings.rs b/src/tools/miri/tests/pass/strings.rs new file mode 100644 index 0000000000000..5e2d2e9b5b5c1 --- /dev/null +++ b/src/tools/miri/tests/pass/strings.rs @@ -0,0 +1,63 @@ +//@compile-flags: -Zmiri-strict-provenance + +fn empty() -> &'static str { + "" +} + +fn hello() -> &'static str { + "Hello, world!" +} + +fn hello_bytes() -> &'static [u8; 13] { + b"Hello, world!" +} + +fn hello_bytes_fat() -> &'static [u8] { + b"Hello, world!" +} + +fn fat_pointer_on_32_bit() { + Some(5).expect("foo"); +} + +fn str_indexing() { + let mut x = "Hello".to_string(); + let _v = &mut x[..3]; // Test IndexMut on String. +} + +fn unique_aliasing() { + // This is a regression test for the aliasing rules of a `Unique` pointer. + // At the time of writing this test case, Miri does not treat `Unique` + // pointers as a special case, these are treated like any other raw pointer. + // However, there are existing Github issues which may lead to `Unique` + // becoming a special case through asserting unique ownership over the pointee: + // - https://github.com/rust-lang/unsafe-code-guidelines/issues/258 + // - https://github.com/rust-lang/unsafe-code-guidelines/issues/262 + // Below, the calls to `String::remove` and `String::insert[_str]` follow + // code paths that would trigger undefined behavior in case `Unique` + // would ever assert semantic ownership over the pointee. Internally, + // these methods call `self.vec.as_ptr()` and `self.vec.as_mut_ptr()` on + // the vector of bytes that are backing the `String`. That `Vec` holds a + // `Unique` internally. The second call to `Vec::as_mut_ptr(&mut self)` + // would then invalidate the pointers derived from `Vec::as_ptr(&self)`. + // Note that as long as `Unique` is treated like any other raw pointer, + // this test case should pass. It is merely here as a canary test for + // potential future undefined behavior. + let mut x = String::from("Hello"); + assert_eq!(x.remove(0), 'H'); + x.insert(0, 'H'); + assert_eq!(x, "Hello"); + x.insert_str(x.len(), ", world!"); + assert_eq!(x, "Hello, world!"); +} + +fn main() { + assert_eq!(empty(), ""); + assert_eq!(hello(), "Hello, world!"); + assert_eq!(hello_bytes(), b"Hello, world!"); + assert_eq!(hello_bytes_fat(), b"Hello, world!"); + + fat_pointer_on_32_bit(); // Should run without crashing. + str_indexing(); + unique_aliasing(); +} diff --git a/src/tools/miri/tests/pass/subslice_array.rs b/src/tools/miri/tests/pass/subslice_array.rs new file mode 100644 index 0000000000000..9b6b12d748adb --- /dev/null +++ b/src/tools/miri/tests/pass/subslice_array.rs @@ -0,0 +1,11 @@ +fn bar(a: &'static str, b: &'static str) -> [&'static str; 4] { + [a, b, b, a] +} + +fn main() { + let out = bar("baz", "foo"); + let [a, xs @ .., d] = out; + assert_eq!(a, "baz"); + assert_eq!(xs, ["foo", "foo"]); + assert_eq!(d, "baz"); +} diff --git a/src/tools/miri/tests/pass/sums.rs b/src/tools/miri/tests/pass/sums.rs new file mode 100644 index 0000000000000..3256d4c65116a --- /dev/null +++ b/src/tools/miri/tests/pass/sums.rs @@ -0,0 +1,61 @@ +#[derive(Debug, PartialEq)] +enum Unit { + Unit(()), // Force non-C-enum representation. +} + +fn return_unit() -> Unit { + Unit::Unit(()) +} + +#[derive(Debug, PartialEq)] +enum MyBool { + False(()), // Force non-C-enum representation. + True(()), +} + +fn return_true() -> MyBool { + MyBool::True(()) +} + +fn return_false() -> MyBool { + MyBool::False(()) +} + +fn return_none() -> Option { + None +} + +fn return_some() -> Option { + Some(42) +} + +fn match_opt_none() -> i8 { + let x = None; + match x { + Some(data) => data, + None => 42, + } +} + +fn match_opt_some() -> i8 { + let x = Some(13); + match x { + Some(data) => data, + None => 20, + } +} + +fn two_nones() -> (Option, Option) { + (None, None) +} + +fn main() { + assert_eq!(two_nones(), (None, None)); + assert_eq!(match_opt_some(), 13); + assert_eq!(match_opt_none(), 42); + assert_eq!(return_some(), Some(42)); + assert_eq!(return_none(), None); + assert_eq!(return_false(), MyBool::False(())); + assert_eq!(return_true(), MyBool::True(())); + assert_eq!(return_unit(), Unit::Unit(())); +} diff --git a/src/tools/miri/tests/pass/tag-align-dyn-u64.rs b/src/tools/miri/tests/pass/tag-align-dyn-u64.rs new file mode 100644 index 0000000000000..72211a8d3f3af --- /dev/null +++ b/src/tools/miri/tests/pass/tag-align-dyn-u64.rs @@ -0,0 +1,37 @@ +// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::mem; + +enum Tag { + Tag2(A), +} + +#[allow(dead_code)] +struct Rec { + c8: u8, + t: Tag, +} + +fn mk_rec() -> Rec { + return Rec { c8: 0, t: Tag::Tag2(0) }; +} + +fn is_u64_aligned(u: &Tag) -> bool { + let p: *const () = unsafe { mem::transmute(u) }; + let p = p as usize; + let u64_align = std::mem::align_of::(); + return (p & (u64_align - 1)) == 0; +} + +pub fn main() { + let x = mk_rec(); + assert!(is_u64_aligned(&x.t)); +} diff --git a/src/tools/miri/tests/pass/threadleak_ignored.rs b/src/tools/miri/tests/pass/threadleak_ignored.rs new file mode 100644 index 0000000000000..99bac7aa42a80 --- /dev/null +++ b/src/tools/miri/tests/pass/threadleak_ignored.rs @@ -0,0 +1,38 @@ +//@ignore-target-windows: Channels on Windows are not supported yet. +// FIXME: disallow preemption to work around https://github.com/rust-lang/rust/issues/55005 +//@compile-flags: -Zmiri-ignore-leaks -Zmiri-preemption-rate=0 + +//! Test that leaking threads works, and that their destructors are not executed. + +use std::cell::RefCell; + +struct LoudDrop(i32); +impl Drop for LoudDrop { + fn drop(&mut self) { + eprintln!("Dropping {}", self.0); + } +} + +thread_local! { + static X: RefCell> = RefCell::new(None); +} + +fn main() { + X.with(|x| *x.borrow_mut() = Some(LoudDrop(0))); + + // Set up a channel so that we can learn when the other thread initialized `X` + // (so that we are sure there is something to drop). + let (send, recv) = std::sync::mpsc::channel::<()>(); + + let _detached = std::thread::spawn(move || { + X.with(|x| *x.borrow_mut() = Some(LoudDrop(1))); + send.send(()).unwrap(); + std::thread::yield_now(); + loop {} + }); + + std::thread::yield_now(); + + // Wait until child thread has initialized its `X`. + let () = recv.recv().unwrap(); +} diff --git a/src/tools/miri/tests/pass/threadleak_ignored.stderr b/src/tools/miri/tests/pass/threadleak_ignored.stderr new file mode 100644 index 0000000000000..7557f49c7584b --- /dev/null +++ b/src/tools/miri/tests/pass/threadleak_ignored.stderr @@ -0,0 +1 @@ +Dropping 0 diff --git a/src/tools/miri/tests/pass/too-large-primval-write-problem.rs b/src/tools/miri/tests/pass/too-large-primval-write-problem.rs new file mode 100644 index 0000000000000..f4c418bd78a99 --- /dev/null +++ b/src/tools/miri/tests/pass/too-large-primval-write-problem.rs @@ -0,0 +1,23 @@ +// `PrimVal`s in Miri are represented with 8 bytes (u64) and at the time of writing, the `-x` +// will sign extend into the entire 8 bytes. Then, if you tried to write the `-x` into +// something smaller than 8 bytes, like a 4 byte pointer, it would crash in byteorder crate +// code that assumed only the low 4 bytes would be set. Actually, we were masking properly for +// everything except pointers before I fixed it, so this was probably impossible to reproduce on +// 64-bit. +// +// This is just intended as a regression test to make sure we don't reintroduce this problem. + +#[cfg(target_pointer_width = "32")] +fn main() { + use std::mem::transmute; + + // Make the weird PrimVal. + let x = 1i32; + let bad = unsafe { transmute::(-x) }; + + // Force it through the Memory::write_primval code. + drop(Box::new(bad)); +} + +#[cfg(not(target_pointer_width = "32"))] +fn main() {} diff --git a/src/tools/miri/tests/pass/track-alloc-1.rs b/src/tools/miri/tests/pass/track-alloc-1.rs new file mode 100644 index 0000000000000..427c800dc51d2 --- /dev/null +++ b/src/tools/miri/tests/pass/track-alloc-1.rs @@ -0,0 +1,6 @@ +// Ensure that tracking early allocations doesn't ICE Miri. +// Early allocations are probably part of the runtime and therefore uninteresting, but they +// shouldn't cause a crash. +//@compile-flags: -Zmiri-track-alloc-id=1 +//@normalize-stderr-test: "[48] bytes" -> "SIZE bytes" +fn main() {} diff --git a/src/tools/miri/tests/pass/track-alloc-1.stderr b/src/tools/miri/tests/pass/track-alloc-1.stderr new file mode 100644 index 0000000000000..7206edbb7010b --- /dev/null +++ b/src/tools/miri/tests/pass/track-alloc-1.stderr @@ -0,0 +1,5 @@ +note: tracking was triggered + | + = note: created extern static allocation of SIZE bytes (alignment ALIGN bytes) with id 1 + = note: (no span available) + diff --git a/src/tools/miri/tests/pass/track-caller-attribute.rs b/src/tools/miri/tests/pass/track-caller-attribute.rs new file mode 100644 index 0000000000000..6694764a234d4 --- /dev/null +++ b/src/tools/miri/tests/pass/track-caller-attribute.rs @@ -0,0 +1,130 @@ +#![feature(core_intrinsics)] + +use std::panic::Location; + +#[track_caller] +fn tracked() -> &'static Location<'static> { + Location::caller() // most importantly, we never get line 7 +} + +fn nested_intrinsic() -> &'static Location<'static> { + Location::caller() +} + +fn nested_tracked() -> &'static Location<'static> { + tracked() +} + +macro_rules! caller_location_from_macro { + () => { + core::panic::Location::caller() + }; +} + +fn test_fn_ptr() { + fn pass_to_ptr_call(f: fn(T), x: T) { + f(x); + } + + #[track_caller] + fn tracked_unit(_: ()) { + let expected_line = line!() - 1; + let location = std::panic::Location::caller(); + assert_eq!(location.file(), file!()); + assert_eq!(location.line(), expected_line, "call shims report location as fn definition"); + } + + pass_to_ptr_call(tracked_unit, ()); +} + +fn test_trait_obj() { + trait Tracked { + #[track_caller] + fn handle(&self) -> &'static Location<'static> { + std::panic::Location::caller() + } + } + + impl Tracked for () {} + impl Tracked for u8 {} + + // Test that we get the correct location + // even with a call through a trait object + + let tracked: &dyn Tracked = &5u8; + let location = tracked.handle(); + let expected_line = line!() - 1; + assert_eq!(location.file(), file!()); + assert_eq!(location.line(), expected_line); + assert_eq!(location.column(), 28); + + const TRACKED: &dyn Tracked = &(); + let location = TRACKED.handle(); + let expected_line = line!() - 1; + assert_eq!(location.file(), file!()); + assert_eq!(location.line(), expected_line); + assert_eq!(location.column(), 28); +} + +fn test_trait_obj2() { + // track_caller on the impl but not the trait. + pub trait Foo { + fn foo(&self) -> &'static Location<'static>; + } + + struct Bar; + impl Foo for Bar { + #[track_caller] + fn foo(&self) -> &'static Location<'static> { + std::panic::Location::caller() + } + } + let expected_line = line!() - 4; // the `fn` signature above + + let f = &Bar as &dyn Foo; + let loc = f.foo(); // trait doesn't track, so we don't point at this call site + assert_eq!(loc.file(), file!()); + assert_eq!(loc.line(), expected_line); +} + +fn main() { + let location = Location::caller(); + let expected_line = line!() - 1; + assert_eq!(location.file(), file!()); + assert_eq!(location.line(), expected_line); + assert_eq!(location.column(), 20); + + let tracked = tracked(); + let expected_line = line!() - 1; + assert_eq!(tracked.file(), file!()); + assert_eq!(tracked.line(), expected_line); + assert_eq!(tracked.column(), 19); + + let nested = nested_intrinsic(); + assert_eq!(nested.file(), file!()); + assert_eq!(nested.line(), 11); + assert_eq!(nested.column(), 5); + + let contained = nested_tracked(); + assert_eq!(contained.file(), file!()); + assert_eq!(contained.line(), 15); + assert_eq!(contained.column(), 5); + + // `Location::caller()` in a macro should behave similarly to `file!` and `line!`, + // i.e. point to where the macro was invoked, instead of the macro itself. + let inmacro = caller_location_from_macro!(); + let expected_line = line!() - 1; + assert_eq!(inmacro.file(), file!()); + assert_eq!(inmacro.line(), expected_line); + assert_eq!(inmacro.column(), 19); + + let intrinsic = core::intrinsics::caller_location(); + let expected_line = line!() - 1; + assert_eq!(intrinsic.file(), file!()); + assert_eq!(intrinsic.line(), expected_line); + assert_eq!(intrinsic.column(), 21); + + test_fn_ptr(); + test_trait_obj(); + test_trait_obj2(); +} diff --git a/src/tools/miri/tests/pass/transmute_ptr.rs b/src/tools/miri/tests/pass/transmute_ptr.rs new file mode 100644 index 0000000000000..fd9d457e440e9 --- /dev/null +++ b/src/tools/miri/tests/pass/transmute_ptr.rs @@ -0,0 +1,52 @@ +#![feature(strict_provenance)] +use std::{mem, ptr}; + +fn t1() { + // If we are careful, we can exploit data layout... + // This is a tricky case since we are transmuting a ScalarPair type to a non-ScalarPair type. + let raw = unsafe { mem::transmute::<&[u8], [*const u8; 2]>(&[42]) }; + let ptr: *const u8 = unsafe { mem::transmute_copy(&raw) }; + assert_eq!(unsafe { *ptr }, 42); +} + +#[cfg(target_pointer_width = "64")] +const PTR_SIZE: usize = 8; +#[cfg(target_pointer_width = "32")] +const PTR_SIZE: usize = 4; + +fn t2() { + let bad = unsafe { mem::transmute::<&[u8], [u8; 2 * PTR_SIZE]>(&[1u8]) }; + let _val = bad[0] + bad[bad.len() - 1]; +} + +fn ptr_integer_array() { + let r = &mut 42; + let _i: [usize; 1] = unsafe { mem::transmute(r) }; + + let _x: [u8; PTR_SIZE] = unsafe { mem::transmute(&0) }; +} + +fn ptr_in_two_halves() { + unsafe { + let ptr = &0 as *const i32; + let arr = [ptr; 2]; + // We want to do a scalar read of a pointer at offset PTR_SIZE/2 into this array. But we + // cannot use a packed struct or `read_unaligned`, as those use the memcpy code path in + // Miri. So instead we shift the entire array by a bit and then the actual read we want to + // do is perfectly aligned. + let mut target_arr = [ptr::null::(); 3]; + let target = target_arr.as_mut_ptr().cast::(); + target.add(PTR_SIZE / 2).cast::<[*const i32; 2]>().write_unaligned(arr); + // Now target_arr[1] is a mix of the two `ptr` we had stored in `arr`. + let strange_ptr = target_arr[1]; + // Check that the provenance works out. + assert_eq!(*strange_ptr.with_addr(ptr.addr()), 0); + } +} + +fn main() { + t1(); + t2(); + ptr_integer_array(); + ptr_in_two_halves(); +} diff --git a/src/tools/miri/tests/pass/trivial.rs b/src/tools/miri/tests/pass/trivial.rs new file mode 100644 index 0000000000000..891d115206561 --- /dev/null +++ b/src/tools/miri/tests/pass/trivial.rs @@ -0,0 +1,11 @@ +fn empty() {} + +fn unit_var() { + let x = (); + x +} + +fn main() { + empty(); + unit_var(); +} diff --git a/src/tools/miri/tests/pass/try-operator-custom.rs b/src/tools/miri/tests/pass/try-operator-custom.rs new file mode 100644 index 0000000000000..ccbf63796af5b --- /dev/null +++ b/src/tools/miri/tests/pass/try-operator-custom.rs @@ -0,0 +1,3 @@ +fn main() { + assert!(Ok::(42) == Ok(42)); +} diff --git a/src/tools/miri/tests/pass/tuple_like_enum_variant_constructor.rs b/src/tools/miri/tests/pass/tuple_like_enum_variant_constructor.rs new file mode 100644 index 0000000000000..5cf91b3f4d194 --- /dev/null +++ b/src/tools/miri/tests/pass/tuple_like_enum_variant_constructor.rs @@ -0,0 +1,3 @@ +fn main() { + assert_eq!(Some(42).map(Some), Some(Some(42))); +} diff --git a/src/tools/miri/tests/pass/tuple_like_enum_variant_constructor_pointer_opt.rs b/src/tools/miri/tests/pass/tuple_like_enum_variant_constructor_pointer_opt.rs new file mode 100644 index 0000000000000..fb57d4f4c1652 --- /dev/null +++ b/src/tools/miri/tests/pass/tuple_like_enum_variant_constructor_pointer_opt.rs @@ -0,0 +1,4 @@ +fn main() { + let x = 5; + assert_eq!(Some(&x).map(Some), Some(Some(&x))); +} diff --git a/src/tools/miri/tests/pass/tuple_like_enum_variant_constructor_struct_pointer_opt.rs b/src/tools/miri/tests/pass/tuple_like_enum_variant_constructor_struct_pointer_opt.rs new file mode 100644 index 0000000000000..44441ed1d36c8 --- /dev/null +++ b/src/tools/miri/tests/pass/tuple_like_enum_variant_constructor_struct_pointer_opt.rs @@ -0,0 +1,33 @@ +#[derive(Copy, Clone, PartialEq, Debug)] +struct A<'a> { + x: i32, + y: &'a i32, +} + +#[derive(Copy, Clone, PartialEq, Debug)] +struct B<'a>(i32, &'a i32); + +#[derive(Copy, Clone, PartialEq, Debug)] +enum C<'a> { + Value(i32, &'a i32), + #[allow(dead_code)] + NoValue, +} + +fn main() { + let x = 5; + let a = A { x: 99, y: &x }; + assert_eq!(Some(a).map(Some), Some(Some(a))); + let f = B; + assert_eq!(Some(B(42, &x)), Some(f(42, &x))); + // the following doesn't compile :( + //let f: for<'a> fn(i32, &'a i32) -> B<'a> = B; + //assert_eq!(Some(B(42, &x)), Some(f(42, &x))); + assert_eq!(B(42, &x), foo(&x, B)); + let f = C::Value; + assert_eq!(C::Value(42, &x), f(42, &x)); +} + +fn foo<'a, F: Fn(i32, &'a i32) -> B<'a>>(i: &'a i32, f: F) -> B<'a> { + f(42, i) +} diff --git a/src/tools/miri/tests/pass/tuple_like_struct_constructor.rs b/src/tools/miri/tests/pass/tuple_like_struct_constructor.rs new file mode 100644 index 0000000000000..05e8893de1787 --- /dev/null +++ b/src/tools/miri/tests/pass/tuple_like_struct_constructor.rs @@ -0,0 +1,5 @@ +fn main() { + #[derive(PartialEq, Eq, Debug)] + struct A(i32); + assert_eq!(Some(42).map(A), Some(A(42))); +} diff --git a/src/tools/miri/tests/pass/u128.rs b/src/tools/miri/tests/pass/u128.rs new file mode 100644 index 0000000000000..0ef7a514cb653 --- /dev/null +++ b/src/tools/miri/tests/pass/u128.rs @@ -0,0 +1,70 @@ +#![feature(bench_black_box)] +use std::hint::black_box as b; + +fn main() { + let x: u128 = 0xFFFF_FFFF_FFFF_FFFF__FFFF_FFFF_FFFF_FFFF; + assert_eq!(0, !x); + assert_eq!(0, !x); + let y: u128 = 0xFFFF_FFFF_FFFF_FFFF__FFFF_FFFF_FFFF_FFFE; + assert_eq!(!1, y); + assert_eq!(x, y | 1); + assert_eq!( + 0xFAFF_0000_FF8F_0000__FFFF_0000_FFFF_FFFE, + y & 0xFAFF_0000_FF8F_0000__FFFF_0000_FFFF_FFFF, + ); + let z: u128 = 0xABCD_EF; + assert_eq!(z * z, 0x734C_C2F2_A521); + assert_eq!(z * z * z * z, 0x33EE_0E2A_54E2_59DA_A0E7_8E41); + assert_eq!(z + z + z + z, 0x2AF3_7BC); + let k: u128 = 0x1234_5678_9ABC_DEFF_EDCB_A987_6543_210; + assert_eq!(k + k, 0x2468_ACF1_3579_BDFF_DB97_530E_CA86_420); + assert_eq!(0, k - k); + assert_eq!(0x1234_5678_9ABC_DEFF_EDCB_A987_5A86_421, k - z); + assert_eq!( + 0x1000_0000_0000_0000_0000_0000_0000_000, + k - 0x234_5678_9ABC_DEFF_EDCB_A987_6543_210, + ); + assert_eq!(0x6EF5_DE4C_D3BC_2AAA_3BB4_CC5D_D6EE_8, k / 42); + assert_eq!(0, k % 42); + assert_eq!(15, z % 42); + assert_eq!(0x169D_A8020_CEC18, k % 0x3ACB_FE49_FF24_AC); + assert_eq!(0x91A2_B3C4_D5E6_F7, k >> 65); + assert_eq!(0xFDB9_7530_ECA8_6420_0000_0000_0000_0000, k << 65); + assert!(k > z); + assert!(y > k); + assert!(y < x); + assert_eq!(x as u64, !0); + assert_eq!(z as u64, 0xABCD_EF); + assert_eq!(k as u64, 0xFEDC_BA98_7654_3210); + assert_eq!(k as i128, 0x1234_5678_9ABC_DEFF_EDCB_A987_6543_210); + assert_eq!((z as f64) as u128, z); + assert_eq!((z as f32) as u128, z); + assert_eq!((z as f64 * 16.0) as u128, z * 16); + assert_eq!((z as f32 * 16.0) as u128, z * 16); + let l: u128 = 432 << 100; + assert_eq!((l as f32) as u128, l); + assert_eq!((l as f64) as u128, l); + // formatting + let j: u128 = 1 << 67; + assert_eq!("147573952589676412928", format!("{}", j)); + assert_eq!("80000000000000000", format!("{:x}", j)); + assert_eq!("20000000000000000000000", format!("{:o}", j)); + assert_eq!( + "10000000000000000000000000000000000000000000000000000000000000000000", + format!("{:b}", j), + ); + assert_eq!("340282366920938463463374607431768211455", format!("{}", u128::MAX)); + assert_eq!("147573952589676412928", format!("{:?}", j)); + // common traits + assert_eq!(x, b(x.clone())); + // overflow checks + assert_eq!((z).checked_mul(z), Some(0x734C_C2F2_A521)); + assert_eq!((k).checked_mul(k), None); + let l: u128 = b(u128::MAX - 10); + let o: u128 = b(17); + assert_eq!(l.checked_add(b(11)), None); + assert_eq!(l.checked_sub(l), Some(0)); + assert_eq!(o.checked_sub(b(18)), None); + assert_eq!(b(1u128).checked_shl(b(127)), Some(1 << 127)); + assert_eq!(o.checked_shl(b(128)), None); +} diff --git a/src/tools/miri/tests/pass/union-overwrite.rs b/src/tools/miri/tests/pass/union-overwrite.rs new file mode 100644 index 0000000000000..f1d3d9d48101a --- /dev/null +++ b/src/tools/miri/tests/pass/union-overwrite.rs @@ -0,0 +1,70 @@ +#[repr(C)] +#[derive(Clone, Copy)] +struct Pair(T, U); +#[repr(C)] +#[derive(Clone, Copy)] +struct Triple(T, T, T); + +#[repr(C)] +union U { + a: Pair, + b: B, +} + +#[repr(C)] +union W { + a: A, + b: B, +} + +#[cfg(target_endian = "little")] +unsafe fn check() { + let mut u = U:: { b: 0xDE_DE }; + u.a.0 = 0xBE; + assert_eq!(u.b, 0xDE_BE); + + let mut u = U:: { b: 0xDEAD_DEAD }; + u.a.0 = 0xBEEF; + assert_eq!(u.b, 0xDEAD_BEEF); + + let mut u = U:: { b: 0xDEADBEEF_DEADBEEF }; + u.a.0 = 0xBAADF00D; + assert_eq!(u.b, 0xDEADBEEF_BAADF00D); + + let mut w = W::, u8>, u32> { b: 0xDEAD_DEAD }; + w.a.0 = Triple(0, 0, 0); + assert_eq!(w.b, 0xDE00_0000); + + let mut w = W::>, u32> { b: 0xDEAD_DEAD }; + w.a.1 = Triple(0, 0, 0); + assert_eq!(w.b, 0x0000_00AD); +} + +#[cfg(target_endian = "big")] +unsafe fn check() { + let mut u = U:: { b: 0xDE_DE }; + u.a.0 = 0xBE; + assert_eq!(u.b, 0xBE_DE); + + let mut u = U:: { b: 0xDEAD_DEAD }; + u.a.0 = 0xBEEF; + assert_eq!(u.b, 0xBEEF_DEAD); + + let mut u = U:: { b: 0xDEADBEEF_DEADBEEF }; + u.a.0 = 0xBAADF00D; + assert_eq!(u.b, 0xBAADF00D_DEADBEEF); + + let mut w = W::, u8>, u32> { b: 0xDEAD_DEAD }; + w.a.0 = Triple(0, 0, 0); + assert_eq!(w.b, 0x0000_00AD); + + let mut w = W::>, u32> { b: 0xDEAD_DEAD }; + w.a.1 = Triple(0, 0, 0); + assert_eq!(w.b, 0xDE00_0000); +} + +fn main() { + unsafe { + check(); + } +} diff --git a/src/tools/miri/tests/pass/union.rs b/src/tools/miri/tests/pass/union.rs new file mode 100644 index 0000000000000..8a6dd49f45038 --- /dev/null +++ b/src/tools/miri/tests/pass/union.rs @@ -0,0 +1,91 @@ +fn main() { + a(); + b(); + c(); + d(); +} + +fn a() { + #[allow(dead_code)] + union U { + f1: u32, + f2: f32, + } + let mut u = U { f1: 1 }; + unsafe { + let b1 = &mut u.f1; + *b1 = 5; + } + assert_eq!(unsafe { u.f1 }, 5); +} + +fn b() { + #[derive(Copy, Clone)] + struct S { + x: u32, + y: u32, + } + + #[allow(dead_code)] + union U { + s: S, + both: u64, + } + let mut u = U { s: S { x: 1, y: 2 } }; + unsafe { + let bx = &mut u.s.x; + let by = &mut u.s.y; + *bx = 5; + *by = 10; + } + assert_eq!(unsafe { u.s.x }, 5); + assert_eq!(unsafe { u.s.y }, 10); +} + +fn c() { + #[repr(u32)] + enum Tag { + I, + F, + } + + #[repr(C)] + union U { + i: i32, + f: f32, + } + + #[repr(C)] + struct Value { + tag: Tag, + u: U, + } + + fn is_zero(v: Value) -> bool { + unsafe { + match v { + Value { tag: Tag::I, u: U { i: 0 } } => true, + Value { tag: Tag::F, u: U { f } } => f == 0.0, + _ => false, + } + } + } + assert!(is_zero(Value { tag: Tag::I, u: U { i: 0 } })); + assert!(is_zero(Value { tag: Tag::F, u: U { f: 0.0 } })); + assert!(!is_zero(Value { tag: Tag::I, u: U { i: 1 } })); + assert!(!is_zero(Value { tag: Tag::F, u: U { f: 42.0 } })); +} + +fn d() { + union MyUnion { + f1: u32, + f2: f32, + } + let u = MyUnion { f1: 10 }; + unsafe { + match u { + MyUnion { f1: 10 } => {} + MyUnion { f2: _f2 } => panic!("foo"), + } + } +} diff --git a/src/tools/miri/tests/pass/unops.rs b/src/tools/miri/tests/pass/unops.rs new file mode 100644 index 0000000000000..f23f68ccbad00 --- /dev/null +++ b/src/tools/miri/tests/pass/unops.rs @@ -0,0 +1,5 @@ +fn main() { + assert_eq!(!true, false); + assert_eq!(!0xFFu16, 0xFF00); + assert_eq!(-{ 1i16 }, -1i16); +} diff --git a/src/tools/miri/tests/pass/unsized.rs b/src/tools/miri/tests/pass/unsized.rs new file mode 100644 index 0000000000000..d9beac4327d7f --- /dev/null +++ b/src/tools/miri/tests/pass/unsized.rs @@ -0,0 +1,36 @@ +#![feature(unsized_tuple_coercion)] +#![feature(unsized_fn_params)] + +use std::mem; + +fn unsized_tuple() { + let x: &(i32, i32, [i32]) = &(0, 1, [2, 3]); + let y: &(i32, i32, [i32]) = &(0, 1, [2, 3, 4]); + let mut a = [y, x]; + a.sort(); + assert_eq!(a, [x, y]); + + assert_eq!(&format!("{:?}", a), "[(0, 1, [2, 3]), (0, 1, [2, 3, 4])]"); + assert_eq!(mem::size_of_val(x), 16); +} + +fn unsized_params() { + pub fn f0(_f: dyn FnOnce()) {} + pub fn f1(_s: str) {} + pub fn f2(_x: i32, _y: [i32]) {} + pub fn f3(_p: dyn Send) {} + + let c: Box = Box::new(|| {}); + f0(*c); + let foo = "foo".to_string().into_boxed_str(); + f1(*foo); + let sl: Box<[i32]> = [0, 1, 2].to_vec().into_boxed_slice(); + f2(5, *sl); + let p: Box = Box::new((1, 2)); + f3(*p); +} + +fn main() { + unsized_tuple(); + unsized_params(); +} diff --git a/src/tools/miri/tests/pass/validation_lifetime_resolution.rs b/src/tools/miri/tests/pass/validation_lifetime_resolution.rs new file mode 100644 index 0000000000000..f5eef2ad6e753 --- /dev/null +++ b/src/tools/miri/tests/pass/validation_lifetime_resolution.rs @@ -0,0 +1,36 @@ +trait Id { + type Out; + + fn id(self) -> Self::Out; +} + +impl<'a> Id for &'a mut i32 { + type Out = &'a mut i32; + + fn id(self) -> Self { + self + } +} + +impl<'a> Id for &'a mut u32 { + type Out = &'a mut u32; + + fn id(self) -> Self { + self + } +} + +fn foo(mut x: T) +where + for<'a> &'a mut T: Id, +{ + let x = &mut x; + let _y = x.id(); + // Inspecting the trace should show that `_y` has a type involving a local lifetime, when it gets validated. + // Unfortunately, there doesn't seem to be a way to actually have a test fail if it does not have the right + // type. Currently, this is *not* working correctly; see . +} + +fn main() { + foo(3) +} diff --git a/src/tools/miri/tests/pass/vec-matching-fold.rs b/src/tools/miri/tests/pass/vec-matching-fold.rs new file mode 100644 index 0000000000000..3a869703bf96a --- /dev/null +++ b/src/tools/miri/tests/pass/vec-matching-fold.rs @@ -0,0 +1,40 @@ +use std::fmt::Debug; + +fn foldl(values: &[T], initial: U, mut function: F) -> U +where + U: Clone + Debug, + T: Debug, + F: FnMut(U, &T) -> U, +{ + match values { + [head, tail @ ..] => foldl(tail, function(initial, head), function), + [] => { + let res = initial.clone(); + res + } + } +} + +fn foldr(values: &[T], initial: U, mut function: F) -> U +where + U: Clone, + F: FnMut(&T, U) -> U, +{ + match values { + [head @ .., tail] => foldr(head, function(tail, initial), function), + [] => { + let res = initial.clone(); + res + } + } +} + +pub fn main() { + let x = &[1, 2, 3, 4, 5]; + + let product = foldl(x, 1, |a, b| a * *b); + assert_eq!(product, 120); + + let sum = foldr(x, 0, |a, b| *a + b); + assert_eq!(sum, 15); +} diff --git a/src/tools/miri/tests/pass/vec.rs b/src/tools/miri/tests/pass/vec.rs new file mode 100644 index 0000000000000..26732cec5eb9a --- /dev/null +++ b/src/tools/miri/tests/pass/vec.rs @@ -0,0 +1,176 @@ +//@compile-flags: -Zmiri-strict-provenance +// Gather all references from a mutable iterator and make sure Miri notices if +// using them is dangerous. +fn test_all_refs<'a, T: 'a>(dummy: &mut T, iter: impl Iterator) { + // Gather all those references. + let mut refs: Vec<&mut T> = iter.collect(); + // Use them all. Twice, to be sure we got all interleavings. + for r in refs.iter_mut() { + std::mem::swap(dummy, r); + } + for r in refs { + std::mem::swap(dummy, r); + } +} + +fn make_vec() -> Vec { + let mut v = Vec::with_capacity(4); + v.push(1); + v.push(2); + v +} + +fn make_vec_macro() -> Vec { + vec![1, 2] +} + +fn make_vec_macro_repeat() -> Vec { + vec![42; 5] +} + +fn make_vec_macro_repeat_zeroed() -> Vec { + vec![0; 7] +} + +fn vec_into_iter() -> u8 { + vec![1, 2, 3, 4].into_iter().map(|x| x * x).fold(0, |x, y| x + y) +} + +fn vec_into_iter_rev() -> u8 { + vec![1, 2, 3, 4].into_iter().map(|x| x * x).fold(0, |x, y| x + y) +} + +fn vec_into_iter_zst() -> usize { + vec![[0u64; 0], [0u64; 0]].into_iter().rev().map(|x| x.len()).sum() +} + +fn vec_into_iter_rev_zst() -> usize { + vec![[0u64; 0], [0u64; 0]].into_iter().rev().map(|x| x.len()).sum() +} + +fn vec_iter_and_mut() { + let mut v = vec![1, 2, 3, 4]; + for i in v.iter_mut() { + *i += 1; + } + assert_eq!(v.iter().sum::(), 2 + 3 + 4 + 5); + + test_all_refs(&mut 13, v.iter_mut()); +} + +fn vec_iter_and_mut_rev() { + let mut v = vec![1, 2, 3, 4]; + for i in v.iter_mut().rev() { + *i += 1; + } + assert_eq!(v.iter().sum::(), 2 + 3 + 4 + 5); +} + +fn vec_reallocate() -> Vec { + let mut v = vec![1, 2]; + v.push(3); + v.push(4); + v.push(5); + v +} + +fn vec_push_ptr_stable() { + let mut v = Vec::with_capacity(10); + v.push(0); + let v0 = unsafe { &mut *(&mut v[0] as *mut _) }; // laundering the lifetime -- we take care that `v` does not reallocate, so that's okay. + v.push(1); + let _val = *v0; +} + +fn vec_extend_ptr_stable() { + let mut v = Vec::with_capacity(10); + v.push(0); + let v0 = unsafe { &mut *(&mut v[0] as *mut _) }; // laundering the lifetime -- we take care that `v` does not reallocate, so that's okay. + // `slice::Iter` (with `T: Copy`) specialization + v.extend(&[1]); + let _val = *v0; + // `vec::IntoIter` specialization + v.extend(vec![2]); + let _val = *v0; + // `TrustedLen` specialization + v.extend(std::iter::once(3)); + let _val = *v0; + // base case + v.extend(std::iter::once(3).filter(|_| true)); + let _val = *v0; +} + +fn vec_truncate_ptr_stable() { + let mut v = vec![0; 10]; + let v0 = unsafe { &mut *(&mut v[0] as *mut _) }; // laundering the lifetime -- we take care that `v` does not reallocate, so that's okay. + v.truncate(5); + let _val = *v0; +} + +fn push_str_ptr_stable() { + let mut buf = String::with_capacity(11); + buf.push_str("hello"); + let hello: &str = unsafe { &*(buf.as_str() as *const _) }; // laundering the lifetime -- we take care that `buf` does not reallocate, so that's okay. + buf.push_str(" world"); + assert_eq!(format!("{}", hello), "hello"); +} + +fn sort() { + let mut v = vec![1; 20]; + v.push(0); + v.sort(); +} + +fn swap() { + let mut v = vec![1, 2, 3, 4]; + v.swap(2, 2); +} + +fn swap_remove() { + let mut a = 0; + let mut b = 1; + let mut vec = vec![&mut a, &mut b]; + + vec.swap_remove(1); +} + +fn reverse() { + #[repr(align(2))] + #[derive(Debug)] + struct Foo(u8); + + let mut v: Vec<_> = (0..50).map(Foo).collect(); + v.reverse(); + assert!(v[0].0 == 49); +} + +fn main() { + assert_eq!(vec_reallocate().len(), 5); + + assert_eq!(vec_into_iter(), 30); + assert_eq!(vec_into_iter_rev(), 30); + vec_iter_and_mut(); + assert_eq!(vec_into_iter_zst(), 0); + assert_eq!(vec_into_iter_rev_zst(), 0); + vec_iter_and_mut_rev(); + + assert_eq!(make_vec().capacity(), 4); + assert_eq!(make_vec_macro(), [1, 2]); + assert_eq!(make_vec_macro_repeat(), [42; 5]); + assert_eq!(make_vec_macro_repeat_zeroed(), [0; 7]); + + // Test interesting empty slice comparison + // (one is a real pointer, one an integer pointer). + assert_eq!((200..-5).step_by(1).collect::>(), []); + + // liballoc has a more extensive test of this, but let's at least do a smoke test here. + vec_push_ptr_stable(); + vec_extend_ptr_stable(); + vec_truncate_ptr_stable(); + push_str_ptr_stable(); + + sort(); + swap(); + swap_remove(); + reverse(); +} diff --git a/src/tools/miri/tests/pass/vecdeque.rs b/src/tools/miri/tests/pass/vecdeque.rs new file mode 100644 index 0000000000000..6f56f9d103e9d --- /dev/null +++ b/src/tools/miri/tests/pass/vecdeque.rs @@ -0,0 +1,51 @@ +//@compile-flags: -Zmiri-strict-provenance +use std::collections::VecDeque; + +fn test_all_refs<'a, T: 'a>(dummy: &mut T, iter: impl Iterator) { + // Gather all those references. + let mut refs: Vec<&mut T> = iter.collect(); + // Use them all. Twice, to be sure we got all interleavings. + for r in refs.iter_mut() { + std::mem::swap(dummy, r); + } + for r in refs { + std::mem::swap(dummy, r); + } +} + +fn main() { + let mut dst = VecDeque::new(); + dst.push_front(Box::new(1)); + dst.push_front(Box::new(2)); + dst.pop_back(); + + let mut src = VecDeque::new(); + src.push_front(Box::new(2)); + dst.append(&mut src); + for a in dst.iter() { + assert_eq!(**a, 2); + } + + // Regression test for Debug impl's + println!("{:?} {:?}", dst, dst.iter()); + println!("{:?}", VecDeque::::new().iter()); + + for a in dst { + assert_eq!(*a, 2); + } + + // # Aliasing tests. + let mut v = std::collections::VecDeque::new(); + v.push_back(1); + v.push_back(2); + + // Test `fold` bad aliasing. + let mut it = v.iter_mut(); + let ref0 = it.next().unwrap(); + let sum = it.fold(0, |x, y| x + *y); + assert_eq!(*ref0 + sum, 3); + + // Test general iterator aliasing. + v.push_front(0); + test_all_refs(&mut 0, v.iter_mut()); +} diff --git a/src/tools/miri/tests/pass/vecdeque.stdout b/src/tools/miri/tests/pass/vecdeque.stdout new file mode 100644 index 0000000000000..63de960ee2bdf --- /dev/null +++ b/src/tools/miri/tests/pass/vecdeque.stdout @@ -0,0 +1,2 @@ +[2, 2] Iter([2, 2], []) +Iter([], []) diff --git a/src/tools/miri/tests/pass/volatile.rs b/src/tools/miri/tests/pass/volatile.rs new file mode 100644 index 0000000000000..c9799801455c6 --- /dev/null +++ b/src/tools/miri/tests/pass/volatile.rs @@ -0,0 +1,12 @@ +// related: #58645 +#![feature(core_intrinsics)] +use std::intrinsics::{volatile_load, volatile_store}; + +pub fn main() { + unsafe { + let i: &mut (isize, isize) = &mut (0, 0); + volatile_store(i, (1, 2)); + assert_eq!(volatile_load(i), (1, 2)); + assert_eq!(i, &mut (1, 2)); + } +} diff --git a/src/tools/miri/tests/pass/weak_memory/extra_cpp.rs b/src/tools/miri/tests/pass/weak_memory/extra_cpp.rs new file mode 100644 index 0000000000000..07cbb4a803f1f --- /dev/null +++ b/src/tools/miri/tests/pass/weak_memory/extra_cpp.rs @@ -0,0 +1,80 @@ +//@compile-flags: -Zmiri-ignore-leaks + +// Tests operations not perfomable through C++'s atomic API +// but doable in safe (at least sound) Rust. + +#![feature(atomic_from_mut)] + +use std::sync::atomic::Ordering::*; +use std::sync::atomic::{AtomicU16, AtomicU32}; +use std::thread::spawn; + +fn static_atomic_mut(val: u32) -> &'static mut AtomicU32 { + let ret = Box::leak(Box::new(AtomicU32::new(val))); + ret +} + +fn split_u32(dword: &mut u32) -> &mut [u16; 2] { + unsafe { std::mem::transmute::<&mut u32, &mut [u16; 2]>(dword) } +} + +fn mem_replace() { + let mut x = AtomicU32::new(0); + + let old_x = std::mem::replace(&mut x, AtomicU32::new(42)); + + assert_eq!(x.load(Relaxed), 42); + assert_eq!(old_x.load(Relaxed), 0); +} + +fn assign_to_mut() { + let x = static_atomic_mut(0); + x.store(1, Relaxed); + + *x = AtomicU32::new(2); + + assert_eq!(x.load(Relaxed), 2); +} + +fn get_mut_write() { + let x = static_atomic_mut(0); + x.store(1, Relaxed); + { + let x_mut = x.get_mut(); + *x_mut = 2; + } + + let j1 = spawn(move || x.load(Relaxed)); + + let r1 = j1.join().unwrap(); + assert_eq!(r1, 2); +} + +// This is technically doable in C++ with atomic_ref +// but little literature exists atm on its involvement +// in mixed size/atomicity accesses +fn from_mut_split() { + let mut x: u32 = 0; + + { + let x_atomic = AtomicU32::from_mut(&mut x); + x_atomic.store(u32::from_be(0xabbafafa), Relaxed); + } + + // Split the `AtomicU32` into two `AtomicU16`. + // Crucially, there is no non-atomic access to `x`! All accesses are atomic, but of different size. + let (x_hi, x_lo) = split_u32(&mut x).split_at_mut(1); + + let x_hi_atomic = AtomicU16::from_mut(&mut x_hi[0]); + let x_lo_atomic = AtomicU16::from_mut(&mut x_lo[0]); + + assert_eq!(x_hi_atomic.load(Relaxed), u16::from_be(0xabba)); + assert_eq!(x_lo_atomic.load(Relaxed), u16::from_be(0xfafa)); +} + +pub fn main() { + get_mut_write(); + from_mut_split(); + assign_to_mut(); + mem_replace(); +} diff --git a/src/tools/miri/tests/pass/weak_memory/extra_cpp_unsafe.rs b/src/tools/miri/tests/pass/weak_memory/extra_cpp_unsafe.rs new file mode 100644 index 0000000000000..f7e2748408ff8 --- /dev/null +++ b/src/tools/miri/tests/pass/weak_memory/extra_cpp_unsafe.rs @@ -0,0 +1,40 @@ +//@compile-flags: -Zmiri-ignore-leaks + +// Tests operations not perfomable through C++'s atomic API +// but doable in unsafe Rust which we think *should* be fine. +// Nonetheless they may be determined as inconsistent with the +// memory model in the future. + +#![feature(atomic_from_mut)] + +use std::sync::atomic::AtomicU32; +use std::sync::atomic::Ordering::*; +use std::thread::spawn; + +fn static_atomic(val: u32) -> &'static AtomicU32 { + let ret = Box::leak(Box::new(AtomicU32::new(val))); + ret +} + +// We allow perfectly overlapping non-atomic and atomic reads to race +fn racing_mixed_atomicity_read() { + let x = static_atomic(0); + x.store(42, Relaxed); + + let j1 = spawn(move || x.load(Relaxed)); + + let j2 = spawn(move || { + let x_ptr = x as *const AtomicU32 as *const u32; + unsafe { x_ptr.read() } + }); + + let r1 = j1.join().unwrap(); + let r2 = j2.join().unwrap(); + + assert_eq!(r1, 42); + assert_eq!(r2, 42); +} + +pub fn main() { + racing_mixed_atomicity_read(); +} diff --git a/src/tools/miri/tests/pass/weak_memory/weak.rs b/src/tools/miri/tests/pass/weak_memory/weak.rs new file mode 100644 index 0000000000000..4c3be6b3559ae --- /dev/null +++ b/src/tools/miri/tests/pass/weak_memory/weak.rs @@ -0,0 +1,150 @@ +//@compile-flags: -Zmiri-ignore-leaks -Zmiri-preemption-rate=0 + +// Tests showing weak memory behaviours are exhibited. All tests +// return true when the desired behaviour is seen. +// This is scheduler and pseudo-RNG dependent, so each test is +// run multiple times until one try returns true. +// Spurious failure is possible, if you are really unlucky with +// the RNG and always read the latest value from the store buffer. + +use std::sync::atomic::Ordering::*; +use std::sync::atomic::{fence, AtomicUsize}; +use std::thread::spawn; + +#[derive(Copy, Clone)] +struct EvilSend(pub T); + +unsafe impl Send for EvilSend {} +unsafe impl Sync for EvilSend {} + +// We can't create static items because we need to run each test +// multiple times +fn static_atomic(val: usize) -> &'static AtomicUsize { + let ret = Box::leak(Box::new(AtomicUsize::new(val))); + ret +} + +// Spins until it reads the given value +fn reads_value(loc: &AtomicUsize, val: usize) -> usize { + while loc.load(Relaxed) != val { + std::hint::spin_loop(); + } + val +} + +fn relaxed() -> bool { + let x = static_atomic(0); + let j1 = spawn(move || { + x.store(1, Relaxed); + x.store(2, Relaxed); + }); + + let j2 = spawn(move || x.load(Relaxed)); + + j1.join().unwrap(); + let r2 = j2.join().unwrap(); + + r2 == 1 +} + +// https://www.doc.ic.ac.uk/~afd/homepages/papers/pdfs/2017/POPL.pdf Figure 8 +fn seq_cst() -> bool { + let x = static_atomic(0); + + let j1 = spawn(move || { + x.store(1, Relaxed); + }); + + let j2 = spawn(move || { + x.store(2, SeqCst); + x.store(3, SeqCst); + }); + + let j3 = spawn(move || x.load(SeqCst)); + + j1.join().unwrap(); + j2.join().unwrap(); + let r3 = j3.join().unwrap(); + + r3 == 1 +} + +fn initialization_write(add_fence: bool) -> bool { + let x = static_atomic(11); + assert_eq!(x.load(Relaxed), 11); // work around https://github.com/rust-lang/miri/issues/2164 + + let wait = static_atomic(0); + + let j1 = spawn(move || { + x.store(22, Relaxed); + // Relaxed is intentional. We want to test if the thread 2 reads the initialisation write + // after a relaxed write + wait.store(1, Relaxed); + }); + + let j2 = spawn(move || { + reads_value(wait, 1); + if add_fence { + fence(AcqRel); + } + x.load(Relaxed) + }); + + j1.join().unwrap(); + let r2 = j2.join().unwrap(); + + r2 == 11 +} + +fn faa_replaced_by_load() -> bool { + // Example from https://github.com/llvm/llvm-project/issues/56450#issuecomment-1183695905 + #[no_mangle] + pub fn rdmw(storing: &AtomicUsize, sync: &AtomicUsize, loading: &AtomicUsize) -> usize { + storing.store(1, Relaxed); + fence(Release); + // sync.fetch_add(0, Relaxed); + sync.load(Relaxed); + fence(Acquire); + loading.load(Relaxed) + } + + let x = static_atomic(0); + assert_eq!(x.load(Relaxed), 0); // work around https://github.com/rust-lang/miri/issues/2164 + let y = static_atomic(0); + assert_eq!(y.load(Relaxed), 0); // work around https://github.com/rust-lang/miri/issues/2164 + let z = static_atomic(0); + assert_eq!(z.load(Relaxed), 0); // work around https://github.com/rust-lang/miri/issues/2164 + + // Since each thread is so short, we need to make sure that they truely run at the same time + // Otherwise t1 will finish before t2 even starts + let go = static_atomic(0); + + let t1 = spawn(move || { + while go.load(Relaxed) == 0 {} + rdmw(y, x, z) + }); + + let t2 = spawn(move || { + while go.load(Relaxed) == 0 {} + rdmw(z, x, y) + }); + + go.store(1, Relaxed); + + let a = t1.join().unwrap(); + let b = t2.join().unwrap(); + (a, b) == (0, 0) +} + +/// Asserts that the function returns true at least once in 100 runs +fn assert_once(f: fn() -> bool) { + assert!(std::iter::repeat_with(|| f()).take(100).any(|x| x)); +} + +pub fn main() { + assert_once(relaxed); + assert_once(seq_cst); + assert_once(|| initialization_write(false)); + assert_once(|| initialization_write(true)); + assert_once(faa_replaced_by_load); +} diff --git a/src/tools/miri/tests/pass/without-validation.rs b/src/tools/miri/tests/pass/without-validation.rs new file mode 100644 index 0000000000000..934c44a7deb4d --- /dev/null +++ b/src/tools/miri/tests/pass/without-validation.rs @@ -0,0 +1,24 @@ +// When we notice something breaks only without validation, we add a test here. +//@compile-flags: -Zmiri-disable-validation +use std::cell::*; + +fn refcell_unsize() { + let cell: RefCell<[i32; 3]> = RefCell::new([1, 2, 3]); + { + let mut cellref: RefMut<'_, [i32; 3]> = cell.borrow_mut(); + cellref[0] = 4; + let mut coerced: RefMut<'_, [i32]> = cellref; + coerced[2] = 5; + } + { + let comp: &mut [i32] = &mut [4, 2, 5]; + let cellref: Ref<'_, [i32; 3]> = cell.borrow(); + assert_eq!(&*cellref, comp); + let coerced: Ref<'_, [i32]> = cellref; + assert_eq!(&*coerced, comp); + } +} + +fn main() { + refcell_unsize(); +} diff --git a/src/tools/miri/tests/pass/write-bytes.rs b/src/tools/miri/tests/pass/write-bytes.rs new file mode 100644 index 0000000000000..b2050c5393e22 --- /dev/null +++ b/src/tools/miri/tests/pass/write-bytes.rs @@ -0,0 +1,85 @@ +#![feature(core_intrinsics)] // for `volatile_set_memory` + +#[repr(C)] +#[derive(Copy, Clone)] +struct Foo { + a: u64, + b: u64, + c: u64, +} + +fn main() { + const LENGTH: usize = 10; + let mut v: [u64; LENGTH] = [0; LENGTH]; + + for idx in 0..LENGTH { + assert_eq!(v[idx], 0); + } + + unsafe { + let p = v.as_mut_ptr(); + ::std::ptr::write_bytes(p, 0xab, LENGTH); + } + + for idx in 0..LENGTH { + assert_eq!(v[idx], 0xabababababababab); + } + + // ----- + + let mut w: [Foo; LENGTH] = [Foo { a: 0, b: 0, c: 0 }; LENGTH]; + for idx in 0..LENGTH { + assert_eq!(w[idx].a, 0); + assert_eq!(w[idx].b, 0); + assert_eq!(w[idx].c, 0); + } + + unsafe { + let p = w.as_mut_ptr(); + ::std::ptr::write_bytes(p, 0xcd, LENGTH); + } + + for idx in 0..LENGTH { + assert_eq!(w[idx].a, 0xcdcdcdcdcdcdcdcd); + assert_eq!(w[idx].b, 0xcdcdcdcdcdcdcdcd); + assert_eq!(w[idx].c, 0xcdcdcdcdcdcdcdcd); + } + + // ----- + // `std::intrinsics::volatile_set_memory` should behave identically + + let mut v: [u64; LENGTH] = [0; LENGTH]; + + for idx in 0..LENGTH { + assert_eq!(v[idx], 0); + } + + unsafe { + let p = v.as_mut_ptr(); + ::std::intrinsics::volatile_set_memory(p, 0xab, LENGTH); + } + + for idx in 0..LENGTH { + assert_eq!(v[idx], 0xabababababababab); + } + + // ----- + + let mut w: [Foo; LENGTH] = [Foo { a: 0, b: 0, c: 0 }; LENGTH]; + for idx in 0..LENGTH { + assert_eq!(w[idx].a, 0); + assert_eq!(w[idx].b, 0); + assert_eq!(w[idx].c, 0); + } + + unsafe { + let p = w.as_mut_ptr(); + ::std::intrinsics::volatile_set_memory(p, 0xcd, LENGTH); + } + + for idx in 0..LENGTH { + assert_eq!(w[idx].a, 0xcdcdcdcdcdcdcdcd); + assert_eq!(w[idx].b, 0xcdcdcdcdcdcdcdcd); + assert_eq!(w[idx].c, 0xcdcdcdcdcdcdcdcd); + } +} diff --git a/src/tools/miri/tests/pass/wtf8.rs b/src/tools/miri/tests/pass/wtf8.rs new file mode 100644 index 0000000000000..be8348654a325 --- /dev/null +++ b/src/tools/miri/tests/pass/wtf8.rs @@ -0,0 +1,22 @@ +//@only-target-windows + +use std::ffi::{OsStr, OsString}; +use std::os::windows::ffi::{OsStrExt, OsStringExt}; + +fn test1() { + let base = "a\té \u{7f}💩\r"; + let mut base: Vec = OsStr::new(base).encode_wide().collect(); + base.push(0xD800); + let _res = OsString::from_wide(&base); +} + +fn test2() { + let mut base: Vec = OsStr::new("aé ").encode_wide().collect(); + base.push(0xD83D); + let mut _res: Vec = OsString::from_wide(&base).encode_wide().collect(); +} + +fn main() { + test1(); + test2(); +} diff --git a/src/tools/miri/tests/pass/zst.rs b/src/tools/miri/tests/pass/zst.rs new file mode 100644 index 0000000000000..a56386a691f8d --- /dev/null +++ b/src/tools/miri/tests/pass/zst.rs @@ -0,0 +1,29 @@ +//@compile-flags: -Zmiri-permissive-provenance +#[derive(PartialEq, Debug)] +struct A; + +fn zst_ret() -> A { + A +} + +fn use_zst() -> A { + let a = A; + a +} + +fn main() { + // Not using the () type here, as writes of that type do not even have MIR generated. + // Also not assigning directly as that's array initialization, not assignment. + let zst_val = [1u8; 0]; + + assert_eq!(zst_ret(), A); + assert_eq!(use_zst(), A); + let x = 42 as *mut [u8; 0]; + // Reading and writing is ok. + unsafe { + *x = zst_val; + } + unsafe { + let _y = *x; + } +} diff --git a/src/tools/miri/tests/pass/zst_box.rs b/src/tools/miri/tests/pass/zst_box.rs new file mode 100644 index 0000000000000..12138be5af976 --- /dev/null +++ b/src/tools/miri/tests/pass/zst_box.rs @@ -0,0 +1,8 @@ +fn main() { + let x = Box::new(()); + let y = Box::new(()); + drop(y); + let z = Box::new(()); + drop(x); + drop(z); +} diff --git a/src/tools/miri/tests/pass/zst_variant_drop.rs b/src/tools/miri/tests/pass/zst_variant_drop.rs new file mode 100644 index 0000000000000..a76f64ce29df7 --- /dev/null +++ b/src/tools/miri/tests/pass/zst_variant_drop.rs @@ -0,0 +1,23 @@ +struct Foo; +impl Drop for Foo { + fn drop(&mut self) { + unsafe { + FOO = true; + } + } +} + +static mut FOO: bool = false; + +enum Bar { + A(Box), + B(Foo), +} + +fn main() { + assert!(unsafe { !FOO }); + drop(Bar::A(Box::new(42))); + assert!(unsafe { !FOO }); + drop(Bar::B(Foo)); + assert!(unsafe { FOO }); +} diff --git a/src/tools/miri/triagebot.toml b/src/tools/miri/triagebot.toml new file mode 100644 index 0000000000000..21a154cafd40d --- /dev/null +++ b/src/tools/miri/triagebot.toml @@ -0,0 +1,11 @@ +[relabel] +allow-unauthenticated = [ + "A-*", + "C-*", + "E-*", + "I-*", + "S-*", + ] + +# Gives us the commands 'ready', 'author', 'blocked' +[shortcut] diff --git a/src/tools/publish_toolstate.py b/src/tools/publish_toolstate.py index c0cef8f7b0a01..9c16ef2cbeccc 100755 --- a/src/tools/publish_toolstate.py +++ b/src/tools/publish_toolstate.py @@ -30,7 +30,6 @@ # These should be collaborators of the rust-lang/rust repository (with at least # read privileges on it). CI will fail otherwise. MAINTAINERS = { - 'miri': {'oli-obk', 'RalfJung'}, 'book': {'carols10cents'}, 'nomicon': {'frewsxcv', 'Gankra', 'JohnTitor'}, 'reference': {'Havvy', 'matthewjasper', 'ehuss'}, @@ -41,7 +40,6 @@ } LABELS = { - 'miri': ['A-miri', 'C-bug'], 'book': ['C-bug'], 'nomicon': ['C-bug'], 'reference': ['C-bug'], @@ -52,7 +50,6 @@ } REPOS = { - 'miri': 'https://github.com/rust-lang/miri', 'book': 'https://github.com/rust-lang/book', 'nomicon': 'https://github.com/rust-lang/nomicon', 'reference': 'https://github.com/rust-lang/reference', @@ -239,16 +236,10 @@ def update_latest( message += '{} (cc {}).\n' \ .format(title, maintainers) # See if we need to create an issue. - if tool == 'miri': - # Create issue if tests used to pass before. Don't open a *second* - # issue when we regress from "test-fail" to "build-fail". - if old == 'test-pass': - create_issue_for_status = new - else: - # Create issue if things no longer build. - # (No issue for mere test failures to avoid spurious issues.) - if new == 'build-fail': - create_issue_for_status = new + # Create issue if things no longer build. + # (No issue for mere test failures to avoid spurious issues.) + if new == 'build-fail': + create_issue_for_status = new if create_issue_for_status is not None: try: diff --git a/triagebot.toml b/triagebot.toml index 12a55fda7ef4d..d358e59c24525 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -356,7 +356,7 @@ cc = ["@ehuss"] cc = ["@rust-lang/clippy"] [mentions."src/tools/miri"] -message = "The Miri submodule was changed" +message = "The Miri subtree was changed" cc = ["@rust-lang/miri"] [mentions."src/tools/rustfmt"]