Auto merge of #129230 - RalfJung:miri-sync, r=RalfJung

Miri subtree update

r? `@ghost`
This commit is contained in:
bors 2024-08-18 13:05:32 +00:00
commit f04f6ca36d
91 changed files with 2543 additions and 1052 deletions

View File

@ -0,0 +1,9 @@
[unstable]
profile-rustflags = true
# Add back the containing directory of the packages we have to refer to using --manifest-path.
# Per-package profiles avoid adding this to build dependencies.
[profile.dev.package."cargo-miri"]
rustflags = ["--remap-path-prefix", "=cargo-miri"]
[profile.dev.package."miri-script"]
rustflags = ["--remap-path-prefix", "=miri-script"]

View File

@ -60,7 +60,7 @@ jobs:
- name: clippy (all features)
run: ./miri clippy --all-features -- -D warnings
- name: rustdoc
run: RUSTDOCFLAGS="-Dwarnings" ./miri cargo doc --document-private-items
run: RUSTDOCFLAGS="-Dwarnings" ./miri 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
@ -123,6 +123,8 @@ jobs:
run: |
git config --global user.name 'The Miri Cronjob Bot'
git config --global user.email 'miri@cron.bot'
- name: Install nightly toolchain
run: rustup toolchain install nightly --profile minimal
- name: get changes from rustc
run: ./miri rustc-pull
- name: Install rustup-toolchain-install-master

View File

@ -35,6 +35,10 @@ runs:
run: cargo install -f rustup-toolchain-install-master hyperfine
shell: bash
- name: Install nightly toolchain
run: rustup toolchain install nightly --profile minimal
shell: bash
- name: Install "master" toolchain
run: |
if [[ ${{ github.event_name }} == 'schedule' ]]; then

View File

@ -8,10 +8,43 @@ find useful.
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].
contact us on the [Rust Zulip]. See the [Rust website](https://www.rust-lang.org/governance/teams/compiler#team-miri)
for a list of Miri maintainers.
[Rust Zulip]: https://rust-lang.zulipchat.com
### Larger-scale contributions
If you are thinking about making a larger-scale contribution -- in particular anything that needs
more than can reasonably fit in a single PR to be feature-complete -- then please talk to us before
writing significant amounts of code. Generally, we will ask that you follow a three-step "project"
process for such contributions:
1. Clearly define the **goal** of the project. This defines the scope of the project, i.e. which
part of which APIs should be supported. If this involves functions that expose a big API surface
with lots of flags, the project may want to support only a tiny subset of flags; that should be
documented. A good way to express the goal is with one or more test cases that Miri should be
able to successfully execute when the project is completed. It is a good idea to get feedback
from team members already at this stage to ensure that the project is reasonably scoped and
aligns with our interests.
2. Make a **design** for how to realize the goal. A larger project will likely have to do global
changes to Miri, like adding new global state to the `Machine` type or new methods to the
`FileDescription` trait. Often we have to iterate on those changes, which can quite substantially
change how the final implementation looks like.
The design should be reasonably concrete, i.e. for new global state or methods the corresponding
Rust types and method signatures should be spelled out. We realize that it can be hard to make a
design without doing implementation work, in particular if you are not yet familiar with the
codebase. Doing draft implementations in phase 2 of this process is perfectly fine, just please
be aware that we might request fundamental changes that can require significantly reworking what
you already did. If you open a PR in this stage, please clearly indicate that this project is
still in the design stage.
3. Finish the **implementation** and have it reviewed.
This process is largely informal, and its primary goal is to more clearly communicate expectations.
Please get in touch with us if you have any questions!
## Preparing the build environment
Miri heavily relies on internal and unstable rustc interfaces to execute MIR,
@ -173,24 +206,24 @@ to `.vscode/settings.json` in your local Miri clone:
"cargo-miri/Cargo.toml",
"miri-script/Cargo.toml",
],
"rust-analyzer.check.invocationLocation": "root",
"rust-analyzer.check.invocationStrategy": "once",
"rust-analyzer.check.overrideCommand": [
"env",
"MIRI_AUTO_OPS=no",
"./miri",
"cargo",
"clippy", // make this `check` when working with a locally built rustc
"--message-format=json",
"--all-targets",
],
// Contrary to what the name suggests, this also affects proc macros.
"rust-analyzer.cargo.buildScripts.invocationLocation": "root",
"rust-analyzer.cargo.buildScripts.invocationStrategy": "once",
"rust-analyzer.cargo.buildScripts.overrideCommand": [
"env",
"MIRI_AUTO_OPS=no",
"./miri",
"cargo",
"check",
"--message-format=json",
"--all-targets",
],
}
```
@ -309,6 +342,7 @@ anyone but Miri itself. They are used to communicate between different Miri
binaries, and as such worth documenting:
* `CARGO_EXTRA_FLAGS` is understood by `./miri` and passed to all host cargo invocations.
It is reserved for CI usage; setting the wrong flags this way can easily confuse the script.
* `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.

View File

@ -414,10 +414,6 @@ to Miri failing to detect cases of undefined behavior in a program.
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=<id1>,<id2>,...` 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=<tag1>,<tag2>,...` 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

View File

@ -1,4 +0,0 @@
#!/bin/sh
# RA invokes `./miri cargo ...` for each workspace, so we need to forward that to the main `miri`
# script. See <https://github.com/rust-analyzer/rust-analyzer/issues/10793>.
exec "$(dirname "$0")"/../miri "$@"

View File

@ -93,12 +93,9 @@ pub fn find_miri() -> PathBuf {
if let Some(path) = env::var_os("MIRI") {
return path.into();
}
// Assume it is in the same directory as ourselves.
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.set_file_name(format!("miri{}", env::consts::EXE_SUFFIX));
path
}

View File

@ -150,8 +150,8 @@ case $HOST_TARGET in
UNIX="panic/panic panic/unwind concurrency/simple atomic libc-mem libc-misc libc-random env num_cpus" # the things that are very similar across all Unixes, and hence easily supported there
TEST_TARGET=x86_64-unknown-freebsd run_tests_minimal $BASIC $UNIX threadname libc-time fs
TEST_TARGET=i686-unknown-freebsd run_tests_minimal $BASIC $UNIX threadname libc-time fs
TEST_TARGET=x86_64-unknown-illumos run_tests_minimal $BASIC $UNIX threadname pthread-sync available-parallelism libc-time
TEST_TARGET=x86_64-pc-solaris run_tests_minimal $BASIC $UNIX threadname pthread-sync available-parallelism libc-time
TEST_TARGET=x86_64-unknown-illumos run_tests_minimal $BASIC $UNIX threadname pthread-sync available-parallelism libc-time tls
TEST_TARGET=x86_64-pc-solaris run_tests_minimal $BASIC $UNIX threadname pthread-sync available-parallelism libc-time tls
TEST_TARGET=aarch64-linux-android run_tests_minimal $BASIC $UNIX
TEST_TARGET=wasm32-wasip2 run_tests_minimal empty_main wasm heap_alloc libc-mem
TEST_TARGET=wasm32-unknown-unknown run_tests_minimal empty_main wasm

View File

@ -1,8 +1,15 @@
#!/usr/bin/env bash
set -e
# We want to call the binary directly, so we need to know where it ends up.
MIRI_SCRIPT_TARGET_DIR="$(dirname "$0")"/miri-script/target
# If stdout is not a terminal and we are not on CI, assume that we are being invoked by RA, and use JSON output.
if ! [ -t 1 ] && [ -z "$CI" ]; then
MESSAGE_FORMAT="--message-format=json"
fi
# We need a nightly toolchain, for the `profile-rustflags` cargo feature.
cargo +nightly build $CARGO_EXTRA_FLAGS --manifest-path "$(dirname "$0")"/miri-script/Cargo.toml \
-q --target-dir "$MIRI_SCRIPT_TARGET_DIR" $MESSAGE_FORMAT || \
( echo "Failed to build miri-script. Is the 'nightly' toolchain installed?"; exit 1 )
# Instead of doing just `cargo run --manifest-path .. $@`, we invoke miri-script binary directly. Invoking `cargo run` goes through
# rustup (that sets it's own environmental variables), which is undesirable.
MIRI_SCRIPT_TARGET_DIR="$(dirname "$0")"/miri-script/target
cargo +stable build $CARGO_EXTRA_FLAGS -q --target-dir "$MIRI_SCRIPT_TARGET_DIR" --manifest-path "$(dirname "$0")"/miri-script/Cargo.toml || \
( echo "Failed to build miri-script. Is the 'stable' toolchain installed?"; exit 1 )
"$MIRI_SCRIPT_TARGET_DIR"/debug/miri-script "$@"

View File

@ -1,4 +0,0 @@
#!/bin/sh
# RA invokes `./miri cargo ...` for each workspace, so we need to forward that to the main `miri`
# script. See <https://github.com/rust-analyzer/rust-analyzer/issues/10793>.
exec "$(dirname "$0")"/../miri "$@"

View File

@ -1,12 +1,12 @@
use std::env;
use std::ffi::{OsStr, OsString};
use std::io::Write;
use std::net;
use std::ops::Not;
use std::ops::Range;
use std::path::PathBuf;
use std::process;
use std::thread;
use std::time;
use std::time::Duration;
use anyhow::{anyhow, bail, Context, Result};
use path_macro::path;
@ -19,9 +19,10 @@ use crate::Command;
/// Used for rustc syncs.
const JOSH_FILTER: &str =
":rev(75dd959a3a40eb5b4574f8d2e23aa6efbeb33573:prefix=src/tools/miri):/src/tools/miri";
const JOSH_PORT: &str = "42042";
const JOSH_PORT: u16 = 42042;
impl MiriEnv {
/// Prepares the environment: builds miri and cargo-miri and a sysroot.
/// Returns the location of the sysroot.
///
/// If the target is None the sysroot will be built for the host machine.
@ -34,12 +35,10 @@ impl MiriEnv {
// Sysroot already set, use that.
return Ok(miri_sysroot.into());
}
let manifest_path = path!(self.miri_dir / "cargo-miri" / "Cargo.toml");
let Self { toolchain, cargo_extra_flags, .. } = &self;
// Make sure everything is built. Also Miri itself.
self.build(path!(self.miri_dir / "Cargo.toml"), &[], quiet)?;
self.build(&manifest_path, &[], quiet)?;
self.build(".", &[], quiet)?;
self.build("cargo-miri", &[], quiet)?;
let target_flag = if let Some(target) = &target {
vec![OsStr::new("--target"), target.as_ref()]
@ -56,10 +55,12 @@ impl MiriEnv {
eprintln!();
}
let mut cmd = cmd!(self.sh,
"cargo +{toolchain} --quiet run {cargo_extra_flags...} --manifest-path {manifest_path} --
miri setup --print-sysroot {target_flag...}"
);
let mut cmd = self
.cargo_cmd("cargo-miri", "run")
.arg("--quiet")
.arg("--")
.args(&["miri", "setup", "--print-sysroot"])
.args(target_flag);
cmd.set_quiet(quiet);
let output = cmd.read()?;
self.sh.set_var("MIRI_SYSROOT", &output);
@ -105,13 +106,11 @@ impl Command {
let mut cmd = process::Command::new("josh-proxy");
cmd.arg("--local").arg(local_dir);
cmd.arg("--remote").arg("https://github.com");
cmd.arg("--port").arg(JOSH_PORT);
cmd.arg("--port").arg(JOSH_PORT.to_string());
cmd.arg("--no-background");
cmd.stdout(process::Stdio::null());
cmd.stderr(process::Stdio::null());
let josh = cmd.spawn().context("failed to start josh-proxy, make sure it is installed")?;
// Give it some time so hopefully the port is open. (100ms was not enough.)
thread::sleep(time::Duration::from_millis(200));
// Create a wrapper that stops it on drop.
struct Josh(process::Child);
@ -125,7 +124,7 @@ impl Command {
.output()
.expect("failed to SIGINT josh-proxy");
// Sadly there is no "wait with timeout"... so we just give it some time to finish.
thread::sleep(time::Duration::from_millis(100));
std::thread::sleep(Duration::from_millis(100));
// Now hopefully it is gone.
if self.0.try_wait().expect("failed to wait for josh-proxy").is_some() {
return;
@ -139,7 +138,20 @@ impl Command {
}
}
Ok(Josh(josh))
// Wait until the port is open. We try every 10ms until 1s passed.
for _ in 0..100 {
// This will generally fail immediately when the port is still closed.
let josh_ready = net::TcpStream::connect_timeout(
&net::SocketAddr::from(([127, 0, 0, 1], JOSH_PORT)),
Duration::from_millis(1),
);
if josh_ready.is_ok() {
return Ok(Josh(josh));
}
// Not ready yet.
std::thread::sleep(Duration::from_millis(10));
}
bail!("Even after waiting for 1s, josh-proxy is still not available.")
}
pub fn exec(self) -> Result<()> {
@ -151,8 +163,8 @@ impl Command {
| Command::Test { .. }
| Command::Run { .. }
| Command::Fmt { .. }
| Command::Clippy { .. }
| Command::Cargo { .. } => Self::auto_actions()?,
| Command::Doc { .. }
| Command::Clippy { .. } => Self::auto_actions()?,
| Command::Toolchain { .. }
| Command::Bench { .. }
| Command::RustcPull { .. }
@ -166,9 +178,9 @@ impl Command {
Command::Test { bless, flags, target } => Self::test(bless, flags, target),
Command::Run { dep, verbose, many_seeds, target, edition, flags } =>
Self::run(dep, verbose, many_seeds, target, edition, flags),
Command::Doc { flags } => Self::doc(flags),
Command::Fmt { flags } => Self::fmt(flags),
Command::Clippy { flags } => Self::clippy(flags),
Command::Cargo { flags } => Self::cargo(flags),
Command::Bench { target, benches } => Self::bench(target, benches),
Command::Toolchain { flags } => Self::toolchain(flags),
Command::RustcPull { commit } => Self::rustc_pull(commit.clone()),
@ -236,6 +248,8 @@ impl Command {
}
// Make sure josh is running.
let josh = Self::start_josh()?;
let josh_url =
format!("http://localhost:{JOSH_PORT}/rust-lang/rust.git@{commit}{JOSH_FILTER}.git");
// Update rust-version file. As a separate commit, since making it part of
// the merge has confused the heck out of josh in the past.
@ -250,7 +264,7 @@ impl Command {
.context("FAILED to commit rust-version file, something went wrong")?;
// Fetch given rustc commit.
cmd!(sh, "git fetch http://localhost:{JOSH_PORT}/rust-lang/rust.git@{commit}{JOSH_FILTER}.git")
cmd!(sh, "git fetch {josh_url}")
.run()
.inspect_err(|_| {
// Try to un-do the previous `git commit`, to leave the repo in the state we found it.
@ -294,6 +308,8 @@ impl Command {
}
// Make sure josh is running.
let josh = Self::start_josh()?;
let josh_url =
format!("http://localhost:{JOSH_PORT}/{github_user}/rust.git{JOSH_FILTER}.git");
// Find a repo we can do our preparation in.
if let Ok(rustc_git) = env::var("RUSTC_GIT") {
@ -338,20 +354,11 @@ impl Command {
// Do the actual push.
sh.change_dir(miri_dir()?);
println!("Pushing miri changes...");
cmd!(
sh,
"git push http://localhost:{JOSH_PORT}/{github_user}/rust.git{JOSH_FILTER}.git HEAD:{branch}"
)
.run()?;
cmd!(sh, "git push {josh_url} HEAD:{branch}").run()?;
println!();
// Do a round-trip check to make sure the push worked as expected.
cmd!(
sh,
"git fetch http://localhost:{JOSH_PORT}/{github_user}/rust.git{JOSH_FILTER}.git {branch}"
)
.ignore_stderr()
.read()?;
cmd!(sh, "git fetch {josh_url} {branch}").ignore_stderr().read()?;
let head = cmd!(sh, "git rev-parse HEAD").read()?;
let fetch_head = cmd!(sh, "git rev-parse FETCH_HEAD").read()?;
if head != fetch_head {
@ -427,39 +434,37 @@ impl Command {
fn build(flags: Vec<String>) -> Result<()> {
let e = MiriEnv::new()?;
e.build(path!(e.miri_dir / "Cargo.toml"), &flags, /* quiet */ false)?;
e.build(path!(e.miri_dir / "cargo-miri" / "Cargo.toml"), &flags, /* quiet */ false)?;
e.build(".", &flags, /* quiet */ false)?;
e.build("cargo-miri", &flags, /* quiet */ false)?;
Ok(())
}
fn check(flags: Vec<String>) -> Result<()> {
let e = MiriEnv::new()?;
e.check(path!(e.miri_dir / "Cargo.toml"), &flags)?;
e.check(path!(e.miri_dir / "cargo-miri" / "Cargo.toml"), &flags)?;
e.check(".", &flags)?;
e.check("cargo-miri", &flags)?;
Ok(())
}
fn doc(flags: Vec<String>) -> Result<()> {
let e = MiriEnv::new()?;
e.doc(".", &flags)?;
e.doc("cargo-miri", &flags)?;
Ok(())
}
fn clippy(flags: Vec<String>) -> Result<()> {
let e = MiriEnv::new()?;
e.clippy(path!(e.miri_dir / "Cargo.toml"), &flags)?;
e.clippy(path!(e.miri_dir / "cargo-miri" / "Cargo.toml"), &flags)?;
e.clippy(path!(e.miri_dir / "miri-script" / "Cargo.toml"), &flags)?;
Ok(())
}
fn cargo(flags: Vec<String>) -> Result<()> {
let e = MiriEnv::new()?;
let toolchain = &e.toolchain;
// 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.
cmd!(e.sh, "cargo +{toolchain} {flags...}").run()?;
e.clippy(".", &flags)?;
e.clippy("cargo-miri", &flags)?;
e.clippy("miri-script", &flags)?;
Ok(())
}
fn test(bless: bool, mut flags: Vec<String>, target: Option<String>) -> Result<()> {
let mut e = MiriEnv::new()?;
// Prepare a sysroot.
// Prepare a sysroot. (Also builds cargo-miri, which we need.)
e.build_miri_sysroot(/* quiet */ false, target.as_deref())?;
// Forward information to test harness.
@ -476,7 +481,7 @@ impl Command {
// Then test, and let caller control flags.
// Only in root project as `cargo-miri` has no tests.
e.test(path!(e.miri_dir / "Cargo.toml"), &flags)?;
e.test(".", &flags)?;
Ok(())
}
@ -504,32 +509,27 @@ impl Command {
early_flags.push("--edition".into());
early_flags.push(edition.as_deref().unwrap_or("2021").into());
// Prepare a sysroot, add it to the flags.
// Prepare a sysroot, add it to the flags. (Also builds cargo-miri, which we need.)
let miri_sysroot = e.build_miri_sysroot(/* quiet */ !verbose, target.as_deref())?;
early_flags.push("--sysroot".into());
early_flags.push(miri_sysroot.into());
// Compute everything needed to run the actual command. Also add MIRIFLAGS.
let miri_manifest = path!(e.miri_dir / "Cargo.toml");
let miri_flags = e.sh.var("MIRIFLAGS").unwrap_or_default();
let miri_flags = flagsplit(&miri_flags);
let toolchain = &e.toolchain;
let extra_flags = &e.cargo_extra_flags;
let quiet_flag = if verbose { None } else { Some("--quiet") };
// This closure runs the command with the given `seed_flag` added between the MIRIFLAGS and
// the `flags` given on the command-line.
let run_miri = |sh: &Shell, seed_flag: Option<String>| -> Result<()> {
let run_miri = |e: &MiriEnv, seed_flag: Option<String>| -> Result<()> {
// The basic command that executes the Miri driver.
let mut cmd = if dep {
cmd!(
sh,
"cargo +{toolchain} {quiet_flag...} test {extra_flags...} --manifest-path {miri_manifest} --test ui -- --miri-run-dep-mode"
)
e.cargo_cmd(".", "test")
.args(&["--test", "ui"])
.args(quiet_flag)
.arg("--")
.args(&["--miri-run-dep-mode"])
} else {
cmd!(
sh,
"cargo +{toolchain} {quiet_flag...} run {extra_flags...} --manifest-path {miri_manifest} --"
)
e.cargo_cmd(".", "run").args(quiet_flag).arg("--")
};
cmd.set_quiet(!verbose);
// Add Miri flags
@ -545,14 +545,14 @@ impl Command {
};
// Run the closure once or many times.
if let Some(seed_range) = many_seeds {
e.run_many_times(seed_range, |sh, seed| {
e.run_many_times(seed_range, |e, seed| {
eprintln!("Trying seed: {seed}");
run_miri(sh, Some(format!("-Zmiri-seed={seed}"))).inspect_err(|_| {
run_miri(e, Some(format!("-Zmiri-seed={seed}"))).inspect_err(|_| {
eprintln!("FAILING SEED: {seed}");
})
})?;
} else {
run_miri(&e.sh, None)?;
run_miri(&e, None)?;
}
Ok(())
}
@ -579,6 +579,6 @@ impl Command {
.filter_ok(|item| item.file_type().is_file())
.map_ok(|item| item.into_path());
e.format_files(files, &e.toolchain[..], &config_path, &flags)
e.format_files(files, &config_path, &flags)
}
}

View File

@ -48,6 +48,11 @@ pub enum Command {
/// Flags that are passed through to `miri`.
flags: Vec<String>,
},
/// Build documentation
Doc {
/// Flags that are passed through to `cargo doc`.
flags: Vec<String>,
},
/// Format all sources and tests.
Fmt {
/// Flags that are passed through to `rustfmt`.
@ -58,9 +63,6 @@ pub enum Command {
/// Flags that are passed through to `cargo clippy`.
flags: Vec<String>,
},
/// Runs just `cargo <flags>` with the Miri-specific environment variables.
/// Mainly meant to be invoked by rust-analyzer.
Cargo { flags: Vec<String> },
/// Runs the benchmarks from bench-cargo-miri in hyperfine. hyperfine needs to be installed.
Bench {
target: Option<String>,
@ -151,6 +153,7 @@ fn main() -> Result<()> {
let command = match args.next_raw().as_deref() {
Some("build") => Command::Build { flags: args.remainder() },
Some("check") => Command::Check { flags: args.remainder() },
Some("doc") => Command::Doc { flags: args.remainder() },
Some("test") => {
let mut target = None;
let mut bless = false;
@ -205,7 +208,6 @@ fn main() -> Result<()> {
}
Some("fmt") => Command::Fmt { flags: args.remainder() },
Some("clippy") => Command::Clippy { flags: args.remainder() },
Some("cargo") => Command::Cargo { flags: args.remainder() },
Some("install") => Command::Install { flags: args.remainder() },
Some("bench") => {
let mut target = None;

View File

@ -7,7 +7,7 @@ use std::thread;
use anyhow::{anyhow, Context, Result};
use dunce::canonicalize;
use path_macro::path;
use xshell::{cmd, Shell};
use xshell::{cmd, Cmd, Shell};
pub fn miri_dir() -> std::io::Result<PathBuf> {
const MIRI_SCRIPT_ROOT_DIR: &str = env!("CARGO_MANIFEST_DIR");
@ -28,13 +28,14 @@ pub fn flagsplit(flags: &str) -> Vec<String> {
}
/// Some extra state we track for building Miri, such as the right RUSTFLAGS.
#[derive(Clone)]
pub struct MiriEnv {
/// miri_dir is the root of the miri repository checkout we are working in.
pub miri_dir: PathBuf,
/// active_toolchain is passed as `+toolchain` argument to cargo/rustc invocations.
pub toolchain: String,
toolchain: String,
/// Extra flags to pass to cargo.
pub cargo_extra_flags: Vec<String>,
cargo_extra_flags: Vec<String>,
/// The rustc sysroot
pub sysroot: PathBuf,
/// The shell we use.
@ -54,15 +55,14 @@ impl MiriEnv {
// Determine some toolchain properties
if !libdir.exists() {
println!("Something went wrong determining the library dir.");
println!("I got {} but that does not exist.", libdir.display());
println!("Please report a bug at https://github.com/rust-lang/miri/issues.");
eprintln!("Something went wrong determining the library dir.");
eprintln!("I got {} but that does not exist.", libdir.display());
eprintln!("Please report a bug at https://github.com/rust-lang/miri/issues.");
std::process::exit(2);
}
// Share target dir between `miri` and `cargo-miri`.
let target_dir = std::env::var_os("CARGO_TARGET_DIR")
.unwrap_or_else(|| path!(miri_dir / "target").into());
sh.set_var("CARGO_TARGET_DIR", target_dir);
// Hard-code the target dir, since we rely on all binaries ending up in the same spot.
sh.set_var("CARGO_TARGET_DIR", path!(miri_dir / "target"));
// We configure dev builds to not be unusably slow.
let devel_opt_level =
@ -91,61 +91,73 @@ impl MiriEnv {
// Get extra flags for cargo.
let cargo_extra_flags = std::env::var("CARGO_EXTRA_FLAGS").unwrap_or_default();
let cargo_extra_flags = flagsplit(&cargo_extra_flags);
if cargo_extra_flags.iter().any(|a| a == "--release" || a.starts_with("--profile")) {
// This makes binaries end up in different paths, let's not do that.
eprintln!(
"Passing `--release` or `--profile` in `CARGO_EXTRA_FLAGS` will totally confuse miri-script, please don't do that."
);
std::process::exit(1);
}
Ok(MiriEnv { miri_dir, toolchain, sh, sysroot, cargo_extra_flags })
}
pub fn cargo_cmd(&self, crate_dir: impl AsRef<OsStr>, cmd: &str) -> Cmd<'_> {
let MiriEnv { toolchain, cargo_extra_flags, .. } = self;
let manifest_path = path!(self.miri_dir / crate_dir.as_ref() / "Cargo.toml");
cmd!(
self.sh,
"cargo +{toolchain} {cmd} {cargo_extra_flags...} --manifest-path {manifest_path}"
)
}
pub fn install_to_sysroot(
&self,
path: impl AsRef<OsStr>,
args: impl IntoIterator<Item = impl AsRef<OsStr>>,
) -> Result<()> {
let MiriEnv { sysroot, toolchain, cargo_extra_flags, .. } = self;
let path = path!(self.miri_dir / path.as_ref());
// Install binaries to the miri toolchain's `sysroot` so they do not interact with other toolchains.
// (Not using `cargo_cmd` as `install` is special and doesn't use `--manifest-path`.)
cmd!(self.sh, "cargo +{toolchain} install {cargo_extra_flags...} --path {path} --force --root {sysroot} {args...}").run()?;
Ok(())
}
pub fn build(
&self,
manifest_path: impl AsRef<OsStr>,
args: &[String],
quiet: bool,
) -> Result<()> {
let MiriEnv { toolchain, cargo_extra_flags, .. } = self;
pub fn build(&self, crate_dir: impl AsRef<OsStr>, args: &[String], quiet: bool) -> Result<()> {
let quiet_flag = if quiet { Some("--quiet") } else { None };
// We build the tests as well, (a) to avoid having rebuilds when building the tests later
// and (b) to have more parallelism during the build of Miri and its tests.
let mut cmd = cmd!(
self.sh,
"cargo +{toolchain} build --bins --tests {cargo_extra_flags...} --manifest-path {manifest_path} {quiet_flag...} {args...}"
);
// This means `./miri run` without `--dep` will build Miri twice (for the sysroot with
// dev-dependencies, and then for running without dev-dependencies), but the way more common
// `./miri test` will avoid building Miri twice.
let mut cmd = self
.cargo_cmd(crate_dir, "build")
.args(&["--bins", "--tests"])
.args(quiet_flag)
.args(args);
cmd.set_quiet(quiet);
cmd.run()?;
Ok(())
}
pub fn check(&self, manifest_path: impl AsRef<OsStr>, args: &[String]) -> Result<()> {
let MiriEnv { toolchain, cargo_extra_flags, .. } = self;
cmd!(self.sh, "cargo +{toolchain} check {cargo_extra_flags...} --manifest-path {manifest_path} --all-targets {args...}")
.run()?;
pub fn check(&self, crate_dir: impl AsRef<OsStr>, args: &[String]) -> Result<()> {
self.cargo_cmd(crate_dir, "check").arg("--all-targets").args(args).run()?;
Ok(())
}
pub fn clippy(&self, manifest_path: impl AsRef<OsStr>, args: &[String]) -> Result<()> {
let MiriEnv { toolchain, cargo_extra_flags, .. } = self;
cmd!(self.sh, "cargo +{toolchain} clippy {cargo_extra_flags...} --manifest-path {manifest_path} --all-targets {args...}")
.run()?;
pub fn doc(&self, crate_dir: impl AsRef<OsStr>, args: &[String]) -> Result<()> {
self.cargo_cmd(crate_dir, "doc").args(args).run()?;
Ok(())
}
pub fn test(&self, manifest_path: impl AsRef<OsStr>, args: &[String]) -> Result<()> {
let MiriEnv { toolchain, cargo_extra_flags, .. } = self;
cmd!(
self.sh,
"cargo +{toolchain} test {cargo_extra_flags...} --manifest-path {manifest_path} {args...}"
)
.run()?;
pub fn clippy(&self, crate_dir: impl AsRef<OsStr>, args: &[String]) -> Result<()> {
self.cargo_cmd(crate_dir, "clippy").arg("--all-targets").args(args).run()?;
Ok(())
}
pub fn test(&self, crate_dir: impl AsRef<OsStr>, args: &[String]) -> Result<()> {
self.cargo_cmd(crate_dir, "test").args(args).run()?;
Ok(())
}
@ -155,7 +167,6 @@ impl MiriEnv {
pub fn format_files(
&self,
files: impl Iterator<Item = Result<PathBuf, walkdir::Error>>,
toolchain: &str,
config_path: &Path,
flags: &[String],
) -> anyhow::Result<()> {
@ -166,6 +177,7 @@ impl MiriEnv {
// Format in batches as not all our files fit into Windows' command argument limit.
for batch in &files.chunks(256) {
// Build base command.
let toolchain = &self.toolchain;
let mut cmd = cmd!(
self.sh,
"rustfmt +{toolchain} --edition=2021 --config-path {config_path} --unstable-features --skip-children {flags...}"
@ -197,7 +209,7 @@ impl MiriEnv {
pub fn run_many_times(
&self,
range: Range<u32>,
run: impl Fn(&Shell, u32) -> Result<()> + Sync,
run: impl Fn(&Self, u32) -> Result<()> + Sync,
) -> Result<()> {
// `next` is atomic so threads can concurrently fetch their next value to run.
let next = AtomicU32::new(range.start);
@ -207,10 +219,10 @@ impl MiriEnv {
let mut handles = Vec::new();
// Spawn one worker per core.
for _ in 0..thread::available_parallelism()?.get() {
// Create a copy of the shell for this thread.
let local_shell = self.sh.clone();
// Create a copy of the environment for this thread.
let local_miri = self.clone();
let handle = s.spawn(|| -> Result<()> {
let local_shell = local_shell; // move the copy into this thread.
let local_miri = local_miri; // move the copy into this thread.
// Each worker thread keeps asking for numbers until we're all done.
loop {
let cur = next.fetch_add(1, Ordering::Relaxed);
@ -219,7 +231,7 @@ impl MiriEnv {
break;
}
// Run the command with this seed.
run(&local_shell, cur).inspect_err(|_| {
run(&local_miri, cur).inspect_err(|_| {
// If we failed, tell everyone about this.
failed.store(true, Ordering::Relaxed);
})?;

View File

@ -1 +1 @@
29e924841f06bb181d87494eba2783761bc1ddec
f24a6ba06f4190d8ec4f22d1baa800e64b1900cb

View File

@ -581,17 +581,6 @@ fn main() {
show_error!("-Zmiri-track-pointer-tag requires nonzero arguments");
}
}
} else if let Some(param) = arg.strip_prefix("-Zmiri-track-call-id=") {
let ids: Vec<u64> = parse_comma_list(param).unwrap_or_else(|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 = parse_comma_list::<NonZero<u64>>(param).unwrap_or_else(|err| {
show_error!("-Zmiri-track-alloc-id requires a comma separated list of valid non-zero `u64` arguments: {err}")

View File

@ -12,8 +12,6 @@ use crate::*;
pub mod stacked_borrows;
pub mod tree_borrows;
pub type CallId = NonZero<u64>;
/// Tracking pointer provenance
#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct BorTag(NonZero<u64>);
@ -57,9 +55,6 @@ impl fmt::Debug for BorTag {
/// Per-call-stack-frame data for borrow tracking
#[derive(Debug)]
pub struct FrameState {
/// 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
@ -93,18 +88,13 @@ pub struct GlobalStateInner {
/// The root tag is the one used for the initial pointer.
/// We need this in a separate table to handle cyclic statics.
root_ptr_tags: FxHashMap<AllocId, BorTag>,
/// 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_invalidated` for more details.
protected_tags: FxHashMap<BorTag, ProtectorKind>,
/// The pointer ids to trace
tracked_pointer_tags: FxHashSet<BorTag>,
/// The call ids to trace
tracked_call_ids: FxHashSet<CallId>,
/// Whether to recurse into datatypes when searching for pointers to retag.
retag_fields: RetagFields,
/// Whether `core::ptr::Unique` gets special (`Box`-like) handling.
@ -168,7 +158,6 @@ impl GlobalStateInner {
pub fn new(
borrow_tracker_method: BorrowTrackerMethod,
tracked_pointer_tags: FxHashSet<BorTag>,
tracked_call_ids: FxHashSet<CallId>,
retag_fields: RetagFields,
unique_is_unique: bool,
) -> Self {
@ -176,10 +165,8 @@ impl GlobalStateInner {
borrow_tracker_method,
next_ptr_tag: BorTag::one(),
root_ptr_tags: FxHashMap::default(),
next_call_id: NonZero::new(1).unwrap(),
protected_tags: FxHashMap::default(),
tracked_pointer_tags,
tracked_call_ids,
retag_fields,
unique_is_unique,
}
@ -192,14 +179,8 @@ impl GlobalStateInner {
id
}
pub fn new_frame(&mut self, machine: &MiriMachine<'_>) -> FrameState {
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 = NonZero::new(call_id.get() + 1).unwrap();
FrameState { call_id, protected_tags: SmallVec::new() }
pub fn new_frame(&mut self) -> FrameState {
FrameState { protected_tags: SmallVec::new() }
}
fn end_call(&mut self, frame: &machine::FrameExtra<'_>) {
@ -252,7 +233,6 @@ impl BorrowTrackerMethod {
RefCell::new(GlobalStateInner::new(
self,
config.tracked_pointer_tags.clone(),
config.tracked_call_ids.clone(),
config.retag_fields,
config.unique_is_unique,
))
@ -346,7 +326,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
fn print_borrow_state(&mut self, alloc_id: AllocId, show_unnamed: bool) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
let Some(borrow_tracker) = &this.machine.borrow_tracker else {
eprintln!("attempted to print borrow state, but no borrow state is being tracked");
return Ok(());
};
let method = borrow_tracker.borrow().borrow_tracker_method;
match method {
BorrowTrackerMethod::StackedBorrows => this.print_stacks(alloc_id),
BorrowTrackerMethod::TreeBorrows => this.print_tree(alloc_id, show_unnamed),

View File

@ -429,30 +429,14 @@ impl<'history, 'ecx, 'tcx> DiagnosticCx<'history, 'ecx, 'tcx> {
ProtectorKind::WeakProtector => "weakly protected",
ProtectorKind::StrongProtector => "strongly protected",
};
let item_tag = item.tag();
let call_id = self
.machine
.threads
.all_stacks()
.flat_map(|(_id, stack)| stack)
.map(|frame| {
frame.extra.borrow_tracker.as_ref().expect("we should have borrow tracking data")
})
.find(|frame| frame.protected_tags.iter().any(|(_, tag)| tag == &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 {item:?} is {protected} by call {call_id:?}",),
vec![],
None,
),
err_sb_ub(format!("deallocating while item {item:?} is {protected}",), vec![], None),
Operation::Retag(RetagOp { orig_tag: tag, .. })
| Operation::Access(AccessOp { tag, .. }) =>
err_sb_ub(
format!(
"not granting access to tag {tag:?} because that would remove {item:?} which is {protected} because it is an argument of call {call_id:?}",
"not granting access to tag {tag:?} because that would remove {item:?} which is {protected}",
),
vec![],
tag.and_then(|tag| self.get_logs_relevant_to(tag, Some(item.tag()))),

View File

@ -7,9 +7,12 @@ use crate::borrow_tracker::BorTag;
pub struct Item(u64);
// An Item contains 3 bitfields:
// * Bits 0-61 store a BorTag
// * Bits 61-63 store a Permission
// * Bit 64 stores a flag which indicates if we have a protector
// * Bits 0-61 store a BorTag.
// * Bits 61-63 store a Permission.
// * Bit 64 stores a flag which indicates if we might have a protector.
// This is purely an optimization: if the bit is set, the tag *might* be
// in `protected_tags`, but if the bit is not set then the tag is definitely
// not in `protected_tags`.
const TAG_MASK: u64 = u64::MAX >> 3;
const PERM_MASK: u64 = 0x3 << 61;
const PROTECTED_MASK: u64 = 0x1 << 63;

View File

@ -1,10 +1,6 @@
use rustc_middle::{
mir::{Mutability, RetagKind},
ty::{
self,
layout::{HasParamEnv, HasTyCtxt},
Ty,
},
ty::{self, layout::HasParamEnv, Ty},
};
use rustc_span::def_id::DefId;
use rustc_target::abi::{Abi, Size};
@ -146,10 +142,9 @@ impl<'tcx> NewPermission {
// interior mutability and protectors interact poorly.
// To eliminate the case of Protected Reserved IM we override interior mutability
// in the case of a protected reference: protected references are always considered
// "freeze".
// "freeze" in their reservation phase.
let initial_state = match mutability {
Mutability::Mut if ty_is_unpin =>
Permission::new_reserved(ty_is_freeze || is_protected),
Mutability::Mut if ty_is_unpin => Permission::new_reserved(ty_is_freeze, is_protected),
Mutability::Not if ty_is_freeze => Permission::new_frozen(),
// Raw pointers never enter this function so they are not handled.
// However raw pointers are not the only pointers that take the parent
@ -176,10 +171,12 @@ impl<'tcx> NewPermission {
// Regular `Unpin` box, give it `noalias` but only a weak protector
// because it is valid to deallocate it within the function.
let ty_is_freeze = ty.is_freeze(*cx.tcx, cx.param_env());
let protected = kind == RetagKind::FnEntry;
let initial_state = Permission::new_reserved(ty_is_freeze, protected);
Self {
zero_size,
initial_state: Permission::new_reserved(ty_is_freeze),
protector: (kind == RetagKind::FnEntry).then_some(ProtectorKind::WeakProtector),
initial_state,
protector: protected.then_some(ProtectorKind::WeakProtector),
}
})
}
@ -521,11 +518,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
fn tb_protect_place(&mut self, place: &MPlaceTy<'tcx>) -> InterpResult<'tcx, MPlaceTy<'tcx>> {
let this = self.eval_context_mut();
// Note: if we were to inline `new_reserved` below we would find out that
// `ty_is_freeze` is eventually unused because it appears in a `ty_is_freeze || true`.
// We are nevertheless including it here for clarity.
let ty_is_freeze = place.layout.ty.is_freeze(*this.tcx, this.param_env());
// Retag it. With protection! That is the entire point.
let new_perm = NewPermission {
initial_state: Permission::new_reserved(
place.layout.ty.is_freeze(this.tcx(), this.param_env()),
),
initial_state: Permission::new_reserved(ty_is_freeze, /* protected */ true),
zero_size: false,
protector: Some(ProtectorKind::StrongProtector),
};

View File

@ -8,10 +8,16 @@ use crate::AccessKind;
/// The activation states of a pointer.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum PermissionPriv {
/// represents: a local reference that has not yet been written to;
/// allows: child reads, foreign reads, foreign writes if type is freeze;
/// represents: a local mutable reference that has not yet been written to;
/// allows: child reads, foreign reads;
/// affected by: child writes (becomes Active),
/// rejects: foreign writes (Disabled, except if type is not freeze).
/// rejects: foreign writes (Disabled).
///
/// `ReservedFrz` is mostly for types that are `Freeze` (no interior mutability).
/// If the type has interior mutability, see `ReservedIM` instead.
/// (Note: since the discovery of `tests/fail/tree_borrows/reservedim_spurious_write.rs`,
/// we also use `ReservedFreeze` for mutable references that were retagged with a protector
/// independently of interior mutability)
///
/// special case: behaves differently when protected, which is where `conflicted`
/// is relevant
@ -22,12 +28,12 @@ enum PermissionPriv {
/// - foreign-read then child-write is UB due to `conflicted`,
/// - child-write then foreign-read is UB since child-write will activate and then
/// foreign-read disables a protected `Active`, which is UB.
///
/// Note: since the discovery of `tests/fail/tree_borrows/reservedim_spurious_write.rs`,
/// `ty_is_freeze` does not strictly mean that the type has no interior mutability,
/// it could be an interior mutable type that lost its interior mutability privileges
/// when retagged with a protector.
Reserved { ty_is_freeze: bool, conflicted: bool },
ReservedFrz { conflicted: bool },
/// Alternative version of `ReservedFrz` made for types with interior mutability.
/// allows: child reads, foreign reads, foreign writes (extra);
/// affected by: child writes (becomes Active);
/// rejects: nothing.
ReservedIM,
/// represents: a unique pointer;
/// allows: child reads, child writes;
/// rejects: foreign reads (Frozen), foreign writes (Disabled).
@ -59,17 +65,14 @@ impl PartialOrd for PermissionPriv {
(_, Frozen) => Less,
(Active, _) => Greater,
(_, Active) => Less,
(
Reserved { ty_is_freeze: f1, conflicted: c1 },
Reserved { ty_is_freeze: f2, conflicted: c2 },
) => {
// No transition ever changes `ty_is_freeze`.
if f1 != f2 {
return None;
}
(ReservedIM, ReservedIM) => Equal,
(ReservedFrz { conflicted: c1 }, ReservedFrz { conflicted: c2 }) => {
// `bool` is ordered such that `false <= true`, so this works as intended.
c1.cmp(c2)
}
// Versions of `Reserved` with different interior mutability are incomparable with each
// other.
(ReservedIM, ReservedFrz { .. }) | (ReservedFrz { .. }, ReservedIM) => return None,
})
}
}
@ -77,7 +80,12 @@ impl PartialOrd for PermissionPriv {
impl PermissionPriv {
/// Check if `self` can be the initial state of a pointer.
fn is_initial(&self) -> bool {
matches!(self, Reserved { conflicted: false, .. } | Frozen)
matches!(self, ReservedFrz { conflicted: false } | Frozen | ReservedIM)
}
/// Reject `ReservedIM` that cannot exist in the presence of a protector.
fn compatible_with_protector(&self) -> bool {
!matches!(self, ReservedIM)
}
}
@ -93,7 +101,7 @@ mod transition {
Disabled => return None,
// The inner data `ty_is_freeze` of `Reserved` is always irrelevant for Read
// accesses, since the data is not being mutated. Hence the `{ .. }`.
readable @ (Reserved { .. } | Active | Frozen) => readable,
readable @ (ReservedFrz { .. } | ReservedIM | Active | Frozen) => readable,
})
}
@ -109,11 +117,16 @@ mod transition {
// Someone else read. To make sure we won't write before function exit,
// we set the "conflicted" flag, which will disallow writes while we are protected.
Reserved { ty_is_freeze, .. } if protected =>
Reserved { ty_is_freeze, conflicted: true },
ReservedFrz { .. } if protected => ReservedFrz { conflicted: true },
// Before activation and without protectors, foreign reads are fine.
// That's the entire point of 2-phase borrows.
res @ Reserved { .. } => res,
res @ (ReservedFrz { .. } | ReservedIM) => {
// Even though we haven't checked `ReservedIM if protected` separately,
// it is a state that cannot occur because under a protector we only
// create `ReservedFrz` never `ReservedIM`.
assert!(!protected);
res
}
Active =>
if protected {
// We wrote, someone else reads -- that's bad.
@ -134,10 +147,10 @@ mod transition {
// If the `conflicted` flag is set, then there was a foreign read during
// the function call that is still ongoing (still `protected`),
// this is UB (`noalias` violation).
Reserved { conflicted: true, .. } if protected => return None,
ReservedFrz { conflicted: true } if protected => return None,
// A write always activates the 2-phase borrow, even with interior
// mutability
Reserved { .. } | Active => Active,
ReservedFrz { .. } | ReservedIM | Active => Active,
Frozen | Disabled => return None,
})
}
@ -145,15 +158,15 @@ mod transition {
/// A non-child node was write-accessed: this makes everything `Disabled` except for
/// non-protected interior mutable `Reserved` which stay the same.
fn foreign_write(state: PermissionPriv, protected: bool) -> Option<PermissionPriv> {
// There is no explicit dependency on `protected`, but recall that interior mutable
// types receive a `ReservedFrz` instead of `ReservedIM` when retagged under a protector,
// so the result of this function does indirectly depend on (past) protector status.
Some(match state {
// FIXME: since the fix related to reservedim_spurious_write, it is now possible
// to express these transitions of the state machine without an explicit dependency
// on `protected`: because `ty_is_freeze: false` implies `!protected` then
// the line handling `Reserved { .. } if protected` could be deleted.
// This will however require optimizations to the exhaustive tests because
// fewer initial conditions are valid.
Reserved { .. } if protected => Disabled,
res @ Reserved { ty_is_freeze: false, .. } => res,
res @ ReservedIM => {
// We can never create a `ReservedIM` under a protector, only `ReservedFrz`.
assert!(!protected);
res
}
_ => Disabled,
})
}
@ -208,9 +221,23 @@ impl Permission {
Self { inner: Active }
}
/// Default initial permission of a reborrowed mutable reference.
pub fn new_reserved(ty_is_freeze: bool) -> Self {
Self { inner: Reserved { ty_is_freeze, conflicted: false } }
/// Default initial permission of a reborrowed mutable reference that is either
/// protected or not interior mutable.
fn new_reserved_frz() -> Self {
Self { inner: ReservedFrz { conflicted: false } }
}
/// Default initial permission of an unprotected interior mutable reference.
fn new_reserved_im() -> Self {
Self { inner: ReservedIM }
}
/// Wrapper around `new_reserved_frz` and `new_reserved_im` that decides
/// which to call based on the interior mutability and the retag kind (whether there
/// is a protector is relevant because being protected takes priority over being
/// interior mutable)
pub fn new_reserved(ty_is_freeze: bool, protected: bool) -> Self {
if ty_is_freeze || protected { Self::new_reserved_frz() } else { Self::new_reserved_im() }
}
/// Default initial permission of a reborrowed shared reference.
@ -224,6 +251,11 @@ impl Permission {
Self { inner: Disabled }
}
/// Reject `ReservedIM` that cannot exist in the presence of a protector.
pub fn compatible_with_protector(&self) -> bool {
self.inner.compatible_with_protector()
}
/// Apply the transition to the inner PermissionPriv.
pub fn perform_access(
kind: AccessKind,
@ -279,12 +311,9 @@ pub mod diagnostics {
f,
"{}",
match self {
Reserved { ty_is_freeze: true, conflicted: false } => "Reserved",
Reserved { ty_is_freeze: true, conflicted: true } => "Reserved (conflicted)",
Reserved { ty_is_freeze: false, conflicted: false } =>
"Reserved (interior mutable)",
Reserved { ty_is_freeze: false, conflicted: true } =>
"Reserved (interior mutable, conflicted)",
ReservedFrz { conflicted: false } => "Reserved",
ReservedFrz { conflicted: true } => "Reserved (conflicted)",
ReservedIM => "Reserved (interior mutable)",
Active => "Active",
Frozen => "Frozen",
Disabled => "Disabled",
@ -312,10 +341,9 @@ pub mod diagnostics {
// and also as `diagnostics::DisplayFmtPermission.uninit` otherwise
// alignment will be incorrect.
match self.inner {
Reserved { ty_is_freeze: true, conflicted: false } => "Rs ",
Reserved { ty_is_freeze: true, conflicted: true } => "RsC ",
Reserved { ty_is_freeze: false, conflicted: false } => "RsM ",
Reserved { ty_is_freeze: false, conflicted: true } => "RsCM",
ReservedFrz { conflicted: false } => "Res ",
ReservedFrz { conflicted: true } => "ResC",
ReservedIM => "ReIM",
Active => "Act ",
Frozen => "Frz ",
Disabled => "Dis ",
@ -325,13 +353,14 @@ pub mod diagnostics {
impl PermTransition {
/// Readable explanation of the consequences of an event.
/// Fits in the sentence "This accessed caused {trans.summary()}".
/// Fits in the sentence "This transition corresponds to {trans.summary()}".
pub fn summary(&self) -> &'static str {
assert!(self.is_possible());
assert!(!self.is_noop());
match (self.from, self.to) {
(_, Active) => "the first write to a 2-phase borrowed mutable reference",
(_, Frozen) => "a loss of write permissions",
(Reserved { conflicted: false, .. }, Reserved { conflicted: true, .. }) =>
(ReservedFrz { conflicted: false }, ReservedFrz { conflicted: true }) =>
"a temporary loss of write permissions until function exit",
(Frozen, Disabled) => "a loss of read permissions",
(_, Disabled) => "a loss of read and write permissions",
@ -380,28 +409,33 @@ pub mod diagnostics {
(Frozen, Frozen) => true,
(Active, Frozen) => true,
(Disabled, Disabled) => true,
(Reserved { conflicted: true, .. }, Reserved { conflicted: true, .. }) =>
true,
(
ReservedFrz { conflicted: true, .. },
ReservedFrz { conflicted: true, .. },
) => true,
// A pointer being `Disabled` is a strictly stronger source of
// errors than it being `Frozen`. If we try to access a `Disabled`,
// then where it became `Frozen` (or `Active` or `Reserved`) is the least
// of our concerns for now.
(Reserved { conflicted: true, .. } | Active | Frozen, Disabled) => false,
(Reserved { conflicted: true, .. }, Frozen) => false,
(ReservedFrz { conflicted: true } | Active | Frozen, Disabled) => false,
(ReservedFrz { conflicted: true }, Frozen) => false,
// `Active` and `Reserved` have all permissions, so a
// `ChildAccessForbidden(Reserved | Active)` can never exist.
(_, Active) | (_, Reserved { conflicted: false, .. }) =>
(_, Active) | (_, ReservedFrz { conflicted: false }) =>
unreachable!("this permission cannot cause an error"),
// No transition has `Reserved(conflicted=false)` as its `.to` unless it's a noop.
(Reserved { conflicted: false, .. }, _) =>
// No transition has `Reserved { conflicted: false }` or `ReservedIM`
// as its `.to` unless it's a noop.
(ReservedFrz { conflicted: false } | ReservedIM, _) =>
unreachable!("self is a noop transition"),
// All transitions produced in normal executions (using `apply_access`)
// change permissions in the order `Reserved -> Active -> Frozen -> Disabled`.
// We assume that the error was triggered on the same location that
// the transition `self` applies to, so permissions found must be increasing
// in the order `self.from < self.to <= insufficient.inner`
(Active | Frozen | Disabled, Reserved { .. }) | (Disabled, Frozen) =>
(Active | Frozen | Disabled, ReservedFrz { .. } | ReservedIM)
| (Disabled, Frozen)
| (ReservedFrz { .. }, ReservedIM) =>
unreachable!("permissions between self and err must be increasing"),
}
}
@ -415,8 +449,10 @@ pub mod diagnostics {
// conflicted.
(Active, Active) => true,
(Frozen, Frozen) => true,
(Reserved { conflicted: true, .. }, Reserved { conflicted: true, .. }) =>
true,
(
ReservedFrz { conflicted: true, .. },
ReservedFrz { conflicted: true, .. },
) => true,
// If the error is a transition `Frozen -> Disabled`, then we don't really
// care whether before that was `Reserved -> Active -> Frozen` or
// `Frozen` directly.
@ -429,23 +465,23 @@ pub mod diagnostics {
// -> Reserved { conflicted: true }` is inexistant or irrelevant,
// and so is the `Reserved { conflicted: false } -> Active`
(Active, Frozen) => false,
(Reserved { conflicted: true, .. }, _) => false,
(ReservedFrz { conflicted: true }, _) => false,
(_, Disabled) =>
unreachable!(
"permission that results in Disabled should not itself be Disabled in the first place"
),
// No transition has `Reserved { conflicted: false }` as its `.to`
// No transition has `Reserved { conflicted: false }` or `ReservedIM` as its `.to`
// unless it's a noop.
(Reserved { conflicted: false, .. }, _) =>
(ReservedFrz { conflicted: false } | ReservedIM, _) =>
unreachable!("self is a noop transition"),
// Permissions only evolve in the order `Reserved -> Active -> Frozen -> Disabled`,
// so permissions found must be increasing in the order
// `self.from < self.to <= forbidden.from < forbidden.to`.
(Disabled, Reserved { .. } | Active | Frozen)
| (Frozen, Reserved { .. } | Active)
| (Active, Reserved { .. }) =>
(Disabled, ReservedFrz { .. } | ReservedIM | Active | Frozen)
| (Frozen, ReservedFrz { .. } | ReservedIM | Active)
| (Active, ReservedFrz { .. } | ReservedIM) =>
unreachable!("permissions between self and err must be increasing"),
}
}
@ -466,9 +502,9 @@ pub mod diagnostics {
#[cfg(test)]
impl Permission {
pub fn is_reserved_with_conflicted(&self, expected_conflicted: bool) -> bool {
pub fn is_reserved_frz_with_conflicted(&self, expected_conflicted: bool) -> bool {
match self.inner {
Reserved { conflicted, .. } => conflicted == expected_conflicted,
ReservedFrz { conflicted } => conflicted == expected_conflicted,
_ => false,
}
}
@ -482,10 +518,9 @@ mod propagation_optimization_checks {
impl Exhaustive for PermissionPriv {
fn exhaustive() -> Box<dyn Iterator<Item = Self>> {
Box::new(
vec![Active, Frozen, Disabled].into_iter().chain(
<[bool; 2]>::exhaustive()
.map(|[ty_is_freeze, conflicted]| Reserved { ty_is_freeze, conflicted }),
),
vec![Active, Frozen, Disabled, ReservedIM]
.into_iter()
.chain(<bool>::exhaustive().map(|conflicted| ReservedFrz { conflicted })),
)
}
}
@ -525,6 +560,9 @@ mod propagation_optimization_checks {
// We thus eliminate from this test and all other tests
// the case where the tag is initially unprotected and later becomes protected.
precondition!(old_protected || !new_protected);
if old_protected {
precondition!(old.compatible_with_protector());
}
for (access, rel_pos) in <(AccessKind, AccessRelatedness)>::exhaustive() {
if let Some(new) = perform_access(access, rel_pos, old, old_protected) {
assert_eq!(
@ -546,6 +584,9 @@ mod propagation_optimization_checks {
for old in PermissionPriv::exhaustive() {
for [old_protected, new_protected] in <[bool; 2]>::exhaustive() {
precondition!(old_protected || !new_protected);
if old_protected {
precondition!(old.compatible_with_protector());
}
for rel_pos in AccessRelatedness::exhaustive() {
precondition!(rel_pos.is_foreign());
if let Some(new) = perform_access(old_access, rel_pos, old, old_protected) {
@ -570,6 +611,9 @@ mod propagation_optimization_checks {
reach.insert((start, start));
for (access, rel) in <(AccessKind, AccessRelatedness)>::exhaustive() {
for prot in bool::exhaustive() {
if prot {
precondition!(start.compatible_with_protector());
}
if let Some(end) = transition::perform_access(access, rel, start, prot) {
reach.insert((start, end));
}

View File

@ -14,6 +14,15 @@ impl Exhaustive for LocationState {
}
}
impl LocationState {
/// Ensure that the current internal state can exist at the same time as a protector.
/// In practice this only eliminates `ReservedIM` that is never used in the presence
/// of a protector (we instead emit `ReservedFrz` on retag).
pub fn compatible_with_protector(&self) -> bool {
self.permission.compatible_with_protector()
}
}
#[test]
#[rustfmt::skip]
// Exhaustive check that for any starting configuration loc,
@ -30,6 +39,9 @@ fn all_read_accesses_commute() {
// so the two read accesses occur under the same protector.
for protected in bool::exhaustive() {
for loc in LocationState::exhaustive() {
if protected {
precondition!(loc.compatible_with_protector());
}
// Apply 1 then 2. Failure here means that there is UB in the source
// and we skip the check in the target.
let mut loc12 = loc;
@ -61,8 +73,8 @@ fn protected_enforces_noalias() {
// We want to check pairs of accesses where one is foreign and one is not.
precondition!(rel1.is_foreign() != rel2.is_foreign());
for [kind1, kind2] in <[AccessKind; 2]>::exhaustive() {
for mut state in LocationState::exhaustive() {
let protected = true;
let protected = true;
for mut state in LocationState::exhaustive().filter(|s| s.compatible_with_protector()) {
precondition!(state.perform_access(kind1, rel1, protected).is_ok());
precondition!(state.perform_access(kind2, rel2, protected).is_ok());
// If these were both allowed, it must have been two reads.
@ -387,6 +399,9 @@ mod spurious_read {
fn retag_y(self, new_y: LocStateProt) -> Result<Self, ()> {
assert!(new_y.is_initial());
if new_y.prot && !new_y.state.compatible_with_protector() {
return Err(());
}
// `xy_rel` changes to "mutually foreign" now: `y` can no longer be a parent of `x`.
Self { y: new_y, xy_rel: RelPosXY::MutuallyForeign, ..self }
.read_if_initialized(PtrSelector::Y)
@ -511,7 +526,7 @@ mod spurious_read {
}
#[test]
// `Reserved(aliased=false)` and `Reserved(aliased=true)` are properly indistinguishable
// `Reserved { conflicted: false }` and `Reserved { conflicted: true }` are properly indistinguishable
// under the conditions where we want to insert a spurious read.
fn reserved_aliased_protected_indistinguishable() {
let source = LocStateProtPair {
@ -521,14 +536,16 @@ mod spurious_read {
prot: true,
},
y: LocStateProt {
state: LocationState::new_uninit(Permission::new_reserved(false)),
state: LocationState::new_uninit(Permission::new_reserved(
/* freeze */ true, /* protected */ true,
)),
prot: true,
},
};
let acc = TestAccess { ptr: PtrSelector::X, kind: AccessKind::Read };
let target = source.clone().perform_test_access(&acc).unwrap();
assert!(source.y.state.permission.is_reserved_with_conflicted(false));
assert!(target.y.state.permission.is_reserved_with_conflicted(true));
assert!(source.y.state.permission.is_reserved_frz_with_conflicted(false));
assert!(target.y.state.permission.is_reserved_frz_with_conflicted(true));
assert!(!source.distinguishable::<(), ()>(&target))
}
@ -563,7 +580,13 @@ mod spurious_read {
precondition!(x_retag_perm.initialized);
// And `x` just got retagged, so it must be initial.
precondition!(x_retag_perm.permission.is_initial());
// As stated earlier, `x` is always protected in the patterns we consider here.
precondition!(x_retag_perm.compatible_with_protector());
for y_protected in bool::exhaustive() {
// Finally `y` that is optionally protected must have a compatible permission.
if y_protected {
precondition!(y_current_perm.compatible_with_protector());
}
v.push(Pattern { xy_rel, x_retag_perm, y_current_perm, y_protected });
}
}

View File

@ -26,27 +26,6 @@ pub(super) struct InitOnce {
clock: VClock,
}
impl<'tcx> EvalContextExtPriv<'tcx> for crate::MiriInterpCx<'tcx> {}
trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
/// Provides the closure with the next InitOnceId. Creates that InitOnce if the closure returns None,
/// otherwise returns the value from the closure.
#[inline]
fn init_once_get_or_create<F>(&mut self, existing: F) -> InterpResult<'tcx, InitOnceId>
where
F: FnOnce(&mut MiriInterpCx<'tcx>, InitOnceId) -> InterpResult<'tcx, Option<InitOnceId>>,
{
let this = self.eval_context_mut();
let next_index = this.machine.sync.init_onces.next_index();
if let Some(old) = existing(this, next_index)? {
Ok(old)
} else {
let new_index = this.machine.sync.init_onces.push(Default::default());
assert_eq!(next_index, new_index);
Ok(new_index)
}
}
}
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
fn init_once_get_or_create_id(
@ -56,9 +35,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
offset: u64,
) -> InterpResult<'tcx, InitOnceId> {
let this = self.eval_context_mut();
this.init_once_get_or_create(|ecx, next_id| {
ecx.get_or_create_id(next_id, lock_op, lock_layout, offset)
})
this.get_or_create_id(lock_op, lock_layout, offset, |ecx| &mut ecx.machine.sync.init_onces)?
.ok_or_else(|| err_ub_format!("init_once has invalid ID").into())
}
#[inline]

View File

@ -164,25 +164,29 @@ pub struct SynchronizationObjects {
impl<'tcx> EvalContextExtPriv<'tcx> for crate::MiriInterpCx<'tcx> {}
pub(super) trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
/// Lazily initialize the ID of this Miri sync structure.
/// ('0' indicates uninit.)
/// If memory stores '0', that indicates uninit and we generate a new instance.
/// Returns `None` if memory stores a non-zero invalid ID.
///
/// `get_objs` must return the `IndexVec` that stores all the objects of this type.
#[inline]
fn get_or_create_id<Id: SyncId>(
fn get_or_create_id<Id: SyncId + Idx, T: Default>(
&mut self,
next_id: Id,
lock_op: &OpTy<'tcx>,
lock_layout: TyAndLayout<'tcx>,
offset: u64,
get_objs: impl for<'a> Fn(&'a mut MiriInterpCx<'tcx>) -> &'a mut IndexVec<Id, T>,
) -> InterpResult<'tcx, Option<Id>> {
let this = self.eval_context_mut();
let value_place =
this.deref_pointer_and_offset(lock_op, offset, lock_layout, this.machine.layouts.u32)?;
let next_index = get_objs(this).next_index();
// Since we are lazy, this update has to be atomic.
let (old, success) = this
.atomic_compare_exchange_scalar(
&value_place,
&ImmTy::from_uint(0u32, this.machine.layouts.u32),
Scalar::from_u32(next_id.to_u32()),
Scalar::from_u32(next_index.to_u32()),
AtomicRwOrd::Relaxed, // deliberately *no* synchronization
AtomicReadOrd::Relaxed,
false,
@ -190,76 +194,22 @@ pub(super) trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
.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
// We set the in-memory ID to `next_index`, now also create this object in the machine
// state.
let new_index = get_objs(this).push(T::default());
assert_eq!(next_index, new_index);
Some(new_index)
} else {
Some(Id::from_u32(old.to_u32().expect("layout is u32")))
let id = Id::from_u32(old.to_u32().expect("layout is u32"));
if get_objs(this).get(id).is_none() {
// The in-memory ID is invalid.
None
} else {
Some(id)
}
})
}
/// Provides the closure with the next MutexId. Creates that mutex if the closure returns None,
/// otherwise returns the value from the closure.
#[inline]
fn mutex_get_or_create<F>(&mut self, existing: F) -> InterpResult<'tcx, MutexId>
where
F: FnOnce(&mut MiriInterpCx<'tcx>, MutexId) -> InterpResult<'tcx, Option<MutexId>>,
{
let this = self.eval_context_mut();
let next_index = this.machine.sync.mutexes.next_index();
if let Some(old) = existing(this, next_index)? {
if this.machine.sync.mutexes.get(old).is_none() {
throw_ub_format!("mutex has invalid ID");
}
Ok(old)
} else {
let new_index = this.machine.sync.mutexes.push(Default::default());
assert_eq!(next_index, new_index);
Ok(new_index)
}
}
/// Provides the closure with the next RwLockId. Creates that RwLock if the closure returns None,
/// otherwise returns the value from the closure.
#[inline]
fn rwlock_get_or_create<F>(&mut self, existing: F) -> InterpResult<'tcx, RwLockId>
where
F: FnOnce(&mut MiriInterpCx<'tcx>, RwLockId) -> InterpResult<'tcx, Option<RwLockId>>,
{
let this = self.eval_context_mut();
let next_index = this.machine.sync.rwlocks.next_index();
if let Some(old) = existing(this, next_index)? {
if this.machine.sync.rwlocks.get(old).is_none() {
throw_ub_format!("rwlock has invalid ID");
}
Ok(old)
} else {
let new_index = this.machine.sync.rwlocks.push(Default::default());
assert_eq!(next_index, new_index);
Ok(new_index)
}
}
/// Provides the closure with the next CondvarId. Creates that Condvar if the closure returns None,
/// otherwise returns the value from the closure.
#[inline]
fn condvar_get_or_create<F>(&mut self, existing: F) -> InterpResult<'tcx, CondvarId>
where
F: FnOnce(&mut MiriInterpCx<'tcx>, CondvarId) -> InterpResult<'tcx, Option<CondvarId>>,
{
let this = self.eval_context_mut();
let next_index = this.machine.sync.condvars.next_index();
if let Some(old) = existing(this, next_index)? {
if this.machine.sync.condvars.get(old).is_none() {
throw_ub_format!("condvar has invalid ID");
}
Ok(old)
} else {
let new_index = this.machine.sync.condvars.push(Default::default());
assert_eq!(next_index, new_index);
Ok(new_index)
}
}
fn condvar_reacquire_mutex(
&mut self,
mutex: MutexId,
@ -293,9 +243,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
offset: u64,
) -> InterpResult<'tcx, MutexId> {
let this = self.eval_context_mut();
this.mutex_get_or_create(|ecx, next_id| {
ecx.get_or_create_id(next_id, lock_op, lock_layout, offset)
})
this.get_or_create_id(lock_op, lock_layout, offset, |ecx| &mut ecx.machine.sync.mutexes)?
.ok_or_else(|| err_ub_format!("mutex has invalid ID").into())
}
fn rwlock_get_or_create_id(
@ -305,9 +254,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
offset: u64,
) -> InterpResult<'tcx, RwLockId> {
let this = self.eval_context_mut();
this.rwlock_get_or_create(|ecx, next_id| {
ecx.get_or_create_id(next_id, lock_op, lock_layout, offset)
})
this.get_or_create_id(lock_op, lock_layout, offset, |ecx| &mut ecx.machine.sync.rwlocks)?
.ok_or_else(|| err_ub_format!("rwlock has invalid ID").into())
}
fn condvar_get_or_create_id(
@ -317,9 +265,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
offset: u64,
) -> InterpResult<'tcx, CondvarId> {
let this = self.eval_context_mut();
this.condvar_get_or_create(|ecx, next_id| {
ecx.get_or_create_id(next_id, lock_op, lock_layout, offset)
})
this.get_or_create_id(lock_op, lock_layout, offset, |ecx| &mut ecx.machine.sync.condvars)?
.ok_or_else(|| err_ub_format!("condvar has invalid ID").into())
}
#[inline]

View File

@ -116,7 +116,6 @@ pub enum NonHaltingDiagnostic {
CreatedPointerTag(NonZero<u64>, Option<String>, Option<(AllocId, AllocRange, ProvenanceExtra)>),
/// This `Item` was popped from the borrow stack. The string explains the reason.
PoppedPointerTag(Item, String),
CreatedCallId(CallId),
CreatedAlloc(AllocId, Size, Align, MemoryKind),
FreedAlloc(AllocId),
AccessedAlloc(AllocId, AccessKind),
@ -607,7 +606,6 @@ impl<'tcx> MiriMachine<'tcx> {
("reborrow of reference to `extern type`".to_string(), DiagLevel::Warning),
CreatedPointerTag(..)
| PoppedPointerTag(..)
| CreatedCallId(..)
| CreatedAlloc(..)
| AccessedAlloc(..)
| FreedAlloc(..)
@ -625,7 +623,6 @@ impl<'tcx> MiriMachine<'tcx> {
"created tag {tag:?} with {perm} at {alloc_id:?}{range:?} derived from {orig_tag:?}"
),
PoppedPointerTag(item, cause) => format!("popped tracked tag for item {item:?}{cause}"),
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}",

View File

@ -118,8 +118,6 @@ pub struct MiriConfig {
pub seed: Option<u64>,
/// The stacked borrows pointer ids to report about
pub tracked_pointer_tags: FxHashSet<BorTag>,
/// The stacked borrows call IDs to report about
pub tracked_call_ids: FxHashSet<CallId>,
/// The allocation ids to report about.
pub tracked_alloc_ids: FxHashSet<AllocId>,
/// For the tracked alloc ids, also report read/write accesses.
@ -183,7 +181,6 @@ impl Default for MiriConfig {
args: vec![],
seed: None,
tracked_pointer_tags: FxHashSet::default(),
tracked_call_ids: FxHashSet::default(),
tracked_alloc_ids: FxHashSet::default(),
track_alloc_accesses: false,
data_race_detector: true,
@ -460,10 +457,13 @@ pub fn eval_entry<'tcx>(
ecx.handle_ice();
panic::resume_unwind(panic_payload)
});
// `Ok` can never happen.
#[cfg(not(bootstrap))]
let Err(res) = res;
#[cfg(bootstrap)]
let res = match res {
Err(res) => res,
// `Ok` can never happen
#[cfg(bootstrap)]
Ok(never) => match never {},
};

View File

@ -371,6 +371,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
path_ty_layout(this, &["std", "sys", "pal", "windows", "c", name])
}
/// Helper function to get `TyAndLayout` of an array that consists of `libc` type.
fn libc_array_ty_layout(&self, name: &str, size: u64) -> TyAndLayout<'tcx> {
let this = self.eval_context_ref();
let elem_ty_layout = this.libc_ty_layout(name);
let array_ty = Ty::new_array(*this.tcx, elem_ty_layout.ty, size);
this.layout_of(array_ty).unwrap()
}
/// Project to the given *named* field (which must be a struct or union type).
fn project_field_named<P: Projectable<'tcx, Provenance>>(
&self,

View File

@ -55,9 +55,9 @@ extern crate either;
extern crate tracing;
// The rustc crates we need
extern crate rustc_attr;
extern crate rustc_apfloat;
extern crate rustc_ast;
extern crate rustc_attr;
extern crate rustc_const_eval;
extern crate rustc_data_structures;
extern crate rustc_errors;
@ -123,9 +123,7 @@ pub use crate::borrow_tracker::stacked_borrows::{
EvalContextExt as _, Item, Permission, Stack, Stacks,
};
pub use crate::borrow_tracker::tree_borrows::{EvalContextExt as _, Tree};
pub use crate::borrow_tracker::{
BorTag, BorrowTrackerMethod, CallId, EvalContextExt as _, RetagFields,
};
pub use crate::borrow_tracker::{BorTag, BorrowTrackerMethod, EvalContextExt as _, RetagFields};
pub use crate::clock::{Clock, Instant};
pub use crate::concurrency::{
cpu_affinity::MAX_CPUS,

View File

@ -455,6 +455,9 @@ pub struct MiriMachine<'tcx> {
/// The table of directory descriptors.
pub(crate) dirs: shims::DirTable,
/// The list of all EpollEventInterest.
pub(crate) epoll_interests: shims::EpollInterestTable,
/// This machine's monotone clock.
pub(crate) clock: Clock,
@ -649,6 +652,7 @@ impl<'tcx> MiriMachine<'tcx> {
isolated_op: config.isolated_op,
validation: config.validation,
fds: shims::FdTable::init(config.mute_stdout_stderr),
epoll_interests: shims::EpollInterestTable::new(),
dirs: Default::default(),
layouts,
threads,
@ -787,6 +791,7 @@ impl VisitProvenance for MiriMachine<'_> {
data_race,
alloc_addresses,
fds,
epoll_interests:_,
tcx: _,
isolated_op: _,
validation: _,
@ -1370,7 +1375,7 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
let borrow_tracker = ecx.machine.borrow_tracker.as_ref();
let extra = FrameExtra {
borrow_tracker: borrow_tracker.map(|bt| bt.borrow_mut().new_frame(&ecx.machine)),
borrow_tracker: borrow_tracker.map(|bt| bt.borrow_mut().new_frame()),
catch_unwind: None,
timing,
is_user_relevant: ecx.machine.is_user_relevant(&frame),

View File

@ -8,7 +8,7 @@ use rustc_middle::mir;
use rustc_middle::ty;
use rustc_span::Symbol;
use rustc_target::{
abi::{Align, Size},
abi::{Align, AlignFromBytesError, Size},
spec::abi::Abi,
};
@ -199,9 +199,20 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
if i128::from(size) > this.tcx.data_layout.pointer_size.signed_int_max() {
throw_ub_format!("creating an allocation larger than half the address space");
}
if !align.is_power_of_two() {
throw_ub_format!("creating allocation with non-power-of-two alignment {}", align);
if let Err(e) = Align::from_bytes(align) {
match e {
AlignFromBytesError::TooLarge(_) => {
throw_unsup_format!(
"creating allocation with alignment {align} exceeding rustc's maximum \
supported value"
);
}
AlignFromBytesError::NotPowerOfTwo(_) => {
throw_ub_format!("creating allocation with non-power-of-two alignment {align}");
}
}
}
Ok(())
}
@ -289,8 +300,12 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
let [id, show_unnamed] = this.check_shim(abi, Abi::Rust, link_name, args)?;
let id = this.read_scalar(id)?.to_u64()?;
let show_unnamed = this.read_scalar(show_unnamed)?.to_bool()?;
if let Some(id) = std::num::NonZero::new(id) {
this.print_borrow_state(AllocId(id), show_unnamed)?;
if let Some(id) = std::num::NonZero::new(id).map(AllocId)
&& this.get_alloc_info(id).2 == AllocKind::LiveData
{
this.print_borrow_state(id, show_unnamed)?;
} else {
eprintln!("{id} is not the ID of a live data allocation");
}
}
"miri_pointer_name" => {

View File

@ -17,7 +17,7 @@ pub mod panic;
pub mod time;
pub mod tls;
pub use unix::{DirTable, FdTable};
pub use unix::{DirTable, EpollInterestTable, FdTable};
/// What needs to be done after emulating an item (a shim or an intrinsic) is done.
pub enum EmulateItemResult {

View File

@ -2,13 +2,15 @@
//! standard file descriptors (stdin/stdout/stderr).
use std::any::Any;
use std::cell::{Ref, RefCell, RefMut};
use std::collections::BTreeMap;
use std::io::{self, ErrorKind, IsTerminal, Read, SeekFrom, Write};
use std::ops::Deref;
use std::rc::Rc;
use std::rc::Weak;
use rustc_target::abi::Size;
use crate::shims::unix::linux::epoll::EpollReadyEvents;
use crate::shims::unix::*;
use crate::*;
@ -25,7 +27,8 @@ pub trait FileDescription: std::fmt::Debug + Any {
/// Reads as much as possible into the given buffer, and returns the number of bytes read.
fn read<'tcx>(
&mut self,
&self,
_self_ref: &FileDescriptionRef,
_communicate_allowed: bool,
_bytes: &mut [u8],
_ecx: &mut MiriInterpCx<'tcx>,
@ -35,7 +38,8 @@ pub trait FileDescription: std::fmt::Debug + Any {
/// Writes as much as possible from the given buffer, and returns the number of bytes written.
fn write<'tcx>(
&mut self,
&self,
_self_ref: &FileDescriptionRef,
_communicate_allowed: bool,
_bytes: &[u8],
_ecx: &mut MiriInterpCx<'tcx>,
@ -46,7 +50,7 @@ pub trait FileDescription: std::fmt::Debug + Any {
/// Reads as much as possible into the given buffer from a given offset,
/// and returns the number of bytes read.
fn pread<'tcx>(
&mut self,
&self,
_communicate_allowed: bool,
_bytes: &mut [u8],
_offset: u64,
@ -58,7 +62,7 @@ pub trait FileDescription: std::fmt::Debug + Any {
/// Writes as much as possible from the given buffer starting at a given offset,
/// and returns the number of bytes written.
fn pwrite<'tcx>(
&mut self,
&self,
_communicate_allowed: bool,
_bytes: &[u8],
_offset: u64,
@ -70,7 +74,7 @@ pub trait FileDescription: std::fmt::Debug + Any {
/// Seeks to the given offset (which can be relative to the beginning, end, or current position).
/// Returns the new position from the start of the stream.
fn seek<'tcx>(
&mut self,
&self,
_communicate_allowed: bool,
_offset: SeekFrom,
) -> InterpResult<'tcx, io::Result<u64>> {
@ -80,6 +84,7 @@ pub trait FileDescription: std::fmt::Debug + Any {
fn close<'tcx>(
self: Box<Self>,
_communicate_allowed: bool,
_ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, io::Result<()>> {
throw_unsup_format!("cannot close {}", self.name());
}
@ -97,18 +102,18 @@ pub trait FileDescription: std::fmt::Debug + Any {
// so we use a default impl here.
false
}
/// Check the readiness of file description.
fn get_epoll_ready_events<'tcx>(&self) -> InterpResult<'tcx, EpollReadyEvents> {
throw_unsup_format!("{}: epoll does not support this file description", self.name());
}
}
impl dyn FileDescription {
#[inline(always)]
pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
pub fn downcast<T: Any>(&self) -> Option<&T> {
(self as &dyn Any).downcast_ref()
}
#[inline(always)]
pub fn downcast_mut<T: Any>(&mut self) -> Option<&mut T> {
(self as &mut dyn Any).downcast_mut()
}
}
impl FileDescription for io::Stdin {
@ -117,7 +122,8 @@ impl FileDescription for io::Stdin {
}
fn read<'tcx>(
&mut self,
&self,
_self_ref: &FileDescriptionRef,
communicate_allowed: bool,
bytes: &mut [u8],
_ecx: &mut MiriInterpCx<'tcx>,
@ -126,7 +132,7 @@ impl FileDescription for io::Stdin {
// 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))
Ok(Read::read(&mut { self }, bytes))
}
fn is_tty(&self, communicate_allowed: bool) -> bool {
@ -140,13 +146,14 @@ impl FileDescription for io::Stdout {
}
fn write<'tcx>(
&mut self,
&self,
_self_ref: &FileDescriptionRef,
_communicate_allowed: bool,
bytes: &[u8],
_ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, io::Result<usize>> {
// We allow writing to stderr even with isolation enabled.
let result = Write::write(self, bytes);
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
@ -168,7 +175,8 @@ impl FileDescription for io::Stderr {
}
fn write<'tcx>(
&mut self,
&self,
_self_ref: &FileDescriptionRef,
_communicate_allowed: bool,
bytes: &[u8],
_ecx: &mut MiriInterpCx<'tcx>,
@ -193,7 +201,8 @@ impl FileDescription for NullOutput {
}
fn write<'tcx>(
&mut self,
&self,
_self_ref: &FileDescriptionRef,
_communicate_allowed: bool,
bytes: &[u8],
_ecx: &mut MiriInterpCx<'tcx>,
@ -203,36 +212,85 @@ impl FileDescription for NullOutput {
}
}
/// Structure contains both the file description and its unique identifier.
#[derive(Clone, Debug)]
pub struct FileDescriptionRef(Rc<RefCell<Box<dyn FileDescription>>>);
pub struct FileDescWithId<T: FileDescription + ?Sized> {
id: FdId,
file_description: Box<T>,
}
#[derive(Clone, Debug)]
pub struct FileDescriptionRef(Rc<FileDescWithId<dyn FileDescription>>);
impl Deref for FileDescriptionRef {
type Target = dyn FileDescription;
fn deref(&self) -> &Self::Target {
&*self.0.file_description
}
}
impl FileDescriptionRef {
fn new(fd: impl FileDescription) -> Self {
FileDescriptionRef(Rc::new(RefCell::new(Box::new(fd))))
fn new(fd: impl FileDescription, id: FdId) -> Self {
FileDescriptionRef(Rc::new(FileDescWithId { id, file_description: Box::new(fd) }))
}
pub fn borrow(&self) -> Ref<'_, dyn FileDescription> {
Ref::map(self.0.borrow(), |fd| fd.as_ref())
}
pub fn borrow_mut(&self) -> RefMut<'_, dyn FileDescription> {
RefMut::map(self.0.borrow_mut(), |fd| fd.as_mut())
}
pub fn close<'ctx>(self, communicate_allowed: bool) -> InterpResult<'ctx, io::Result<()>> {
pub fn close<'tcx>(
self,
communicate_allowed: bool,
ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, io::Result<()>> {
// Destroy this `Rc` using `into_inner` so we can call `close` instead of
// implicitly running the destructor of the file description.
let id = self.get_id();
match Rc::into_inner(self.0) {
Some(fd) => RefCell::into_inner(fd).close(communicate_allowed),
Some(fd) => {
// Remove entry from the global epoll_event_interest table.
ecx.machine.epoll_interests.remove(id);
fd.file_description.close(communicate_allowed, ecx)
}
None => Ok(Ok(())),
}
}
pub fn downgrade(&self) -> WeakFileDescriptionRef {
WeakFileDescriptionRef { weak_ref: Rc::downgrade(&self.0) }
}
pub fn get_id(&self) -> FdId {
self.0.id
}
}
/// Holds a weak reference to the actual file description.
#[derive(Clone, Debug, Default)]
pub struct WeakFileDescriptionRef {
weak_ref: Weak<FileDescWithId<dyn FileDescription>>,
}
impl WeakFileDescriptionRef {
pub fn upgrade(&self) -> Option<FileDescriptionRef> {
if let Some(file_desc_with_id) = self.weak_ref.upgrade() {
return Some(FileDescriptionRef(file_desc_with_id));
}
None
}
}
/// A unique id for file descriptions. While we could use the address, considering that
/// is definitely unique, the address would expose interpreter internal state when used
/// for sorting things. So instead we generate a unique id per file description that stays
/// the same even if a file descriptor is duplicated and gets a new integer file descriptor.
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Ord, PartialOrd)]
pub struct FdId(usize);
/// The file descriptor table
#[derive(Debug)]
pub struct FdTable {
fds: BTreeMap<i32, FileDescriptionRef>,
pub fds: BTreeMap<i32, FileDescriptionRef>,
/// Unique identifier for file description, used to differentiate between various file description.
next_file_description_id: FdId,
}
impl VisitProvenance for FdTable {
@ -243,7 +301,7 @@ impl VisitProvenance for FdTable {
impl FdTable {
fn new() -> Self {
FdTable { fds: BTreeMap::new() }
FdTable { fds: BTreeMap::new(), next_file_description_id: FdId(0) }
}
pub(crate) fn init(mute_stdout_stderr: bool) -> FdTable {
let mut fds = FdTable::new();
@ -258,10 +316,20 @@ impl FdTable {
fds
}
pub fn new_ref(&mut self, fd: impl FileDescription) -> FileDescriptionRef {
let file_handle = FileDescriptionRef::new(fd, self.next_file_description_id);
self.next_file_description_id = FdId(self.next_file_description_id.0.strict_add(1));
file_handle
}
/// Insert a new file description to the FdTable.
pub fn insert_new(&mut self, fd: impl FileDescription) -> i32 {
let file_handle = FileDescriptionRef::new(fd);
self.insert_ref_with_min_fd(file_handle, 0)
let fd_ref = self.new_ref(fd);
self.insert(fd_ref)
}
pub fn insert(&mut self, fd_ref: FileDescriptionRef) -> i32 {
self.insert_ref_with_min_fd(fd_ref, 0)
}
/// Insert a file description, giving it a file descriptor that is at least `min_fd`.
@ -291,17 +359,7 @@ impl FdTable {
new_fd
}
pub fn get(&self, fd: i32) -> Option<Ref<'_, dyn FileDescription>> {
let fd = self.fds.get(&fd)?;
Some(fd.borrow())
}
pub fn get_mut(&self, fd: i32) -> Option<RefMut<'_, dyn FileDescription>> {
let fd = self.fds.get(&fd)?;
Some(fd.borrow_mut())
}
pub fn get_ref(&self, fd: i32) -> Option<FileDescriptionRef> {
pub fn get(&self, fd: i32) -> Option<FileDescriptionRef> {
let fd = self.fds.get(&fd)?;
Some(fd.clone())
}
@ -320,7 +378,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
fn dup(&mut self, old_fd: i32) -> InterpResult<'tcx, Scalar> {
let this = self.eval_context_mut();
let Some(dup_fd) = this.machine.fds.get_ref(old_fd) else {
let Some(dup_fd) = this.machine.fds.get(old_fd) else {
return Ok(Scalar::from_i32(this.fd_not_found()?));
};
Ok(Scalar::from_i32(this.machine.fds.insert_ref_with_min_fd(dup_fd, 0)))
@ -329,7 +387,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
fn dup2(&mut self, old_fd: i32, new_fd: i32) -> InterpResult<'tcx, Scalar> {
let this = self.eval_context_mut();
let Some(dup_fd) = this.machine.fds.get_ref(old_fd) else {
let Some(dup_fd) = this.machine.fds.get(old_fd) else {
return Ok(Scalar::from_i32(this.fd_not_found()?));
};
if new_fd != old_fd {
@ -337,7 +395,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// If old_fd and new_fd point to the same description, then `dup_fd` ensures we keep the underlying file description alive.
if let Some(file_description) = this.machine.fds.fds.insert(new_fd, dup_fd) {
// Ignore close error (not interpreter's) according to dup2() doc.
file_description.close(this.machine.communicate())?.ok();
file_description.close(this.machine.communicate(), this)?.ok();
}
}
Ok(Scalar::from_i32(new_fd))
@ -415,7 +473,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
}
let start = this.read_scalar(&args[2])?.to_i32()?;
match this.machine.fds.get_ref(fd) {
match this.machine.fds.get(fd) {
Some(dup_fd) =>
Ok(Scalar::from_i32(this.machine.fds.insert_ref_with_min_fd(dup_fd, start))),
None => Ok(Scalar::from_i32(this.fd_not_found()?)),
@ -442,7 +500,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let Some(file_description) = this.machine.fds.remove(fd) else {
return Ok(Scalar::from_i32(this.fd_not_found()?));
};
let result = file_description.close(this.machine.communicate())?;
let result = file_description.close(this.machine.communicate(), this)?;
// return `0` if close is successful
let result = result.map(|()| 0i32);
Ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
@ -488,7 +546,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let communicate = this.machine.communicate();
// We temporarily dup the FD to be able to retain mutable access to `this`.
let Some(fd) = this.machine.fds.get_ref(fd) else {
let Some(fd) = this.machine.fds.get(fd) else {
trace!("read: FD not found");
return Ok(Scalar::from_target_isize(this.fd_not_found()?, this));
};
@ -499,17 +557,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// `usize::MAX` because it is bounded by the host's `isize`.
let mut bytes = vec![0; usize::try_from(count).unwrap()];
let result = match offset {
None => fd.borrow_mut().read(communicate, &mut bytes, this),
None => fd.read(&fd, communicate, &mut bytes, this),
Some(offset) => {
let Ok(offset) = u64::try_from(offset) else {
let einval = this.eval_libc("EINVAL");
this.set_last_error(einval)?;
return Ok(Scalar::from_target_isize(-1, this));
};
fd.borrow_mut().pread(communicate, &mut bytes, offset, this)
fd.pread(communicate, &mut bytes, offset, this)
}
};
drop(fd);
// `File::read` never returns a value larger than `count`, so this cannot fail.
match result?.map(|c| i64::try_from(c).unwrap()) {
@ -553,22 +610,21 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let bytes = this.read_bytes_ptr_strip_provenance(buf, Size::from_bytes(count))?.to_owned();
// We temporarily dup the FD to be able to retain mutable access to `this`.
let Some(fd) = this.machine.fds.get_ref(fd) else {
let Some(fd) = this.machine.fds.get(fd) else {
return Ok(Scalar::from_target_isize(this.fd_not_found()?, this));
};
let result = match offset {
None => fd.borrow_mut().write(communicate, &bytes, this),
None => fd.write(&fd, communicate, &bytes, this),
Some(offset) => {
let Ok(offset) = u64::try_from(offset) else {
let einval = this.eval_libc("EINVAL");
this.set_last_error(einval)?;
return Ok(Scalar::from_target_isize(-1, this));
};
fd.borrow_mut().pwrite(communicate, &bytes, offset, this)
fd.pwrite(communicate, &bytes, offset, this)
}
};
drop(fd);
let result = result?.map(|c| i64::try_from(c).unwrap());
Ok(Scalar::from_target_isize(this.try_unwrap_io_result(result)?, this))

View File

@ -288,14 +288,25 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
this.write_scalar(result, dest)?;
}
// Sockets
// Unnamed sockets and pipes
"socketpair" => {
let [domain, type_, protocol, sv] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.socketpair(domain, type_, protocol, sv)?;
this.write_scalar(result, dest)?;
}
"pipe" => {
let [pipefd] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.pipe2(pipefd, /*flags*/ None)?;
this.write_scalar(result, dest)?;
}
"pipe2" => {
let [pipefd, flags] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.pipe2(pipefd, Some(flags))?;
this.write_scalar(result, dest)?;
}
// Time
"gettimeofday" => {
@ -815,6 +826,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
this.handle_miri_start_unwind(payload)?;
return Ok(EmulateItemResult::NeedsUnwind);
}
"getuid" => {
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(UID, 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.
@ -877,13 +893,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
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" | "__posix_getpwuid_r"
if this.frame_in_std() => {
// getpwuid_r is the standard name, __posix_getpwuid_r is used on solarish
@ -898,7 +907,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let result = this.deref_pointer(result)?;
// Must be for "us".
if uid != crate::shims::unix::UID {
if uid != UID {
throw_unsup_format!("`getpwuid_r` on other users is not supported");
}

View File

@ -12,6 +12,7 @@ use rustc_data_structures::fx::FxHashMap;
use rustc_target::abi::Size;
use crate::shims::os_str::bytes_to_os_str;
use crate::shims::unix::fd::FileDescriptionRef;
use crate::shims::unix::*;
use crate::*;
use shims::time::system_time_to_duration;
@ -30,27 +31,29 @@ impl FileDescription for FileHandle {
}
fn read<'tcx>(
&mut self,
&self,
_self_ref: &FileDescriptionRef,
communicate_allowed: bool,
bytes: &mut [u8],
_ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, io::Result<usize>> {
assert!(communicate_allowed, "isolation should have prevented even opening a file");
Ok(self.file.read(bytes))
Ok((&mut &self.file).read(bytes))
}
fn write<'tcx>(
&mut self,
&self,
_self_ref: &FileDescriptionRef,
communicate_allowed: bool,
bytes: &[u8],
_ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, io::Result<usize>> {
assert!(communicate_allowed, "isolation should have prevented even opening a file");
Ok(self.file.write(bytes))
Ok((&mut &self.file).write(bytes))
}
fn pread<'tcx>(
&mut self,
&self,
communicate_allowed: bool,
bytes: &mut [u8],
offset: u64,
@ -60,13 +63,13 @@ impl FileDescription for FileHandle {
// Emulates pread using seek + read + seek to restore cursor position.
// Correctness of this emulation relies on sequential nature of Miri execution.
// The closure is used to emulate `try` block, since we "bubble" `io::Error` using `?`.
let file = &mut &self.file;
let mut f = || {
let cursor_pos = self.file.stream_position()?;
self.file.seek(SeekFrom::Start(offset))?;
let res = self.file.read(bytes);
let cursor_pos = file.stream_position()?;
file.seek(SeekFrom::Start(offset))?;
let res = file.read(bytes);
// Attempt to restore cursor position even if the read has failed
self.file
.seek(SeekFrom::Start(cursor_pos))
file.seek(SeekFrom::Start(cursor_pos))
.expect("failed to restore file position, this shouldn't be possible");
res
};
@ -74,7 +77,7 @@ impl FileDescription for FileHandle {
}
fn pwrite<'tcx>(
&mut self,
&self,
communicate_allowed: bool,
bytes: &[u8],
offset: u64,
@ -84,13 +87,13 @@ impl FileDescription for FileHandle {
// Emulates pwrite using seek + write + seek to restore cursor position.
// Correctness of this emulation relies on sequential nature of Miri execution.
// The closure is used to emulate `try` block, since we "bubble" `io::Error` using `?`.
let file = &mut &self.file;
let mut f = || {
let cursor_pos = self.file.stream_position()?;
self.file.seek(SeekFrom::Start(offset))?;
let res = self.file.write(bytes);
let cursor_pos = file.stream_position()?;
file.seek(SeekFrom::Start(offset))?;
let res = file.write(bytes);
// Attempt to restore cursor position even if the write has failed
self.file
.seek(SeekFrom::Start(cursor_pos))
file.seek(SeekFrom::Start(cursor_pos))
.expect("failed to restore file position, this shouldn't be possible");
res
};
@ -98,17 +101,18 @@ impl FileDescription for FileHandle {
}
fn seek<'tcx>(
&mut self,
&self,
communicate_allowed: bool,
offset: SeekFrom,
) -> InterpResult<'tcx, io::Result<u64>> {
assert!(communicate_allowed, "isolation should have prevented even opening a file");
Ok(self.file.seek(offset))
Ok((&mut &self.file).seek(offset))
}
fn close<'tcx>(
self: Box<Self>,
communicate_allowed: bool,
_ecx: &mut MiriInterpCx<'tcx>,
) -> 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.
@ -576,7 +580,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let communicate = this.machine.communicate();
let Some(mut file_description) = this.machine.fds.get_mut(fd) else {
let Some(file_description) = this.machine.fds.get(fd) else {
return Ok(Scalar::from_i64(this.fd_not_found()?));
};
let result = file_description
@ -1272,7 +1276,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// FIXME: Support ftruncate64 for all FDs
let FileHandle { file, writable } =
file_description.downcast_ref::<FileHandle>().ok_or_else(|| {
file_description.downcast::<FileHandle>().ok_or_else(|| {
err_unsup_format!("`ftruncate64` is only supported on file-backed file descriptors")
})?;
@ -1324,7 +1328,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
};
// Only regular files support synchronization.
let FileHandle { file, writable } =
file_description.downcast_ref::<FileHandle>().ok_or_else(|| {
file_description.downcast::<FileHandle>().ok_or_else(|| {
err_unsup_format!("`fsync` is only supported on file-backed file descriptors")
})?;
let io_result = maybe_sync_file(file, *writable, File::sync_all);
@ -1349,7 +1353,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
};
// Only regular files support synchronization.
let FileHandle { file, writable } =
file_description.downcast_ref::<FileHandle>().ok_or_else(|| {
file_description.downcast::<FileHandle>().ok_or_else(|| {
err_unsup_format!("`fdatasync` is only supported on file-backed file descriptors")
})?;
let io_result = maybe_sync_file(file, *writable, File::sync_data);
@ -1397,7 +1401,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
};
// Only regular files support synchronization.
let FileHandle { file, writable } =
file_description.downcast_ref::<FileHandle>().ok_or_else(|| {
file_description.downcast::<FileHandle>().ok_or_else(|| {
err_unsup_format!(
"`sync_data_range` is only supported on file-backed file descriptors"
)
@ -1704,7 +1708,7 @@ impl FileMetadata {
};
let file = &file_description
.downcast_ref::<FileHandle>()
.downcast::<FileHandle>()
.ok_or_else(|| {
err_unsup_format!(
"obtaining metadata is only supported on file-backed file descriptors"

View File

@ -1,32 +1,127 @@
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::io;
use std::rc::{Rc, Weak};
use rustc_data_structures::fx::FxHashMap;
use crate::shims::unix::fd::{FdId, FileDescriptionRef};
use crate::shims::unix::*;
use crate::*;
/// An `Epoll` file descriptor connects file handles and epoll events
#[derive(Clone, Debug, Default)]
struct Epoll {
/// The file descriptors we are watching, and what we are watching for.
file_descriptors: FxHashMap<i32, EpollEvent>,
/// A map of EpollEventInterests registered under this epoll instance.
/// Each entry is differentiated using FdId and file descriptor value.
interest_list: RefCell<BTreeMap<(FdId, i32), Rc<RefCell<EpollEventInterest>>>>,
/// A map of EpollEventInstance that will be returned when `epoll_wait` is called.
/// Similar to interest_list, the entry is also differentiated using FdId
/// and file descriptor value.
// This is an Rc because EpollInterest need to hold a reference to update
// it.
ready_list: Rc<RefCell<BTreeMap<(FdId, i32), EpollEventInstance>>>,
}
/// Epoll Events associate events with data.
/// These fields are currently unused by miri.
/// This matches the `epoll_event` struct defined
/// EpollEventInstance contains information that will be returned by epoll_wait.
#[derive(Debug)]
pub struct EpollEventInstance {
/// Xor-ed event types that happened to the file description.
events: u32,
/// Original data retrieved from `epoll_event` during `epoll_ctl`.
data: u64,
}
impl EpollEventInstance {
pub fn new(events: u32, data: u64) -> EpollEventInstance {
EpollEventInstance { events, data }
}
}
/// EpollEventInterest registers the file description information to an epoll
/// instance during a successful `epoll_ctl` call. It also stores additional
/// information needed to check and update readiness state for `epoll_wait`.
///
/// `events` and `data` field matches the `epoll_event` struct defined
/// by the epoll_ctl man page. For more information
/// see the man page:
///
/// <https://man7.org/linux/man-pages/man2/epoll_ctl.2.html>
#[derive(Clone, Debug)]
struct EpollEvent {
#[allow(dead_code)]
pub struct EpollEventInterest {
/// The file descriptor value of the file description registered.
file_descriptor: i32,
/// The events bitmask retrieved from `epoll_event`.
events: u32,
/// `Scalar` is used to represent the
/// `epoll_data` type union.
#[allow(dead_code)]
data: Scalar,
/// The data retrieved from `epoll_event`.
/// libc's data field in epoll_event can store integer or pointer,
/// but only u64 is supported for now.
/// <https://man7.org/linux/man-pages/man3/epoll_event.3type.html>
data: u64,
/// Ready list of the epoll instance under which this EpollEventInterest is registered.
ready_list: Rc<RefCell<BTreeMap<(FdId, i32), EpollEventInstance>>>,
}
/// EpollReadyEvents reflects the readiness of a file description.
pub struct EpollReadyEvents {
/// The associated file is available for read(2) operations, in the sense that a read will not block.
/// (I.e., returning EOF is considered "ready".)
pub epollin: bool,
/// The associated file is available for write(2) operations, in the sense that a write will not block.
pub epollout: bool,
/// Stream socket peer closed connection, or shut down writing
/// half of connection.
pub epollrdhup: bool,
/// For stream socket, this event merely indicates that the peer
/// closed its end of the channel.
/// Unlike epollrdhup, this should only be set when the stream is fully closed.
/// epollrdhup also gets set when only the write half is closed, which is possible
/// via `shutdown(_, SHUT_WR)`.
pub epollhup: bool,
/// Error condition happened on the associated file descriptor.
pub epollerr: bool,
}
impl EpollReadyEvents {
pub fn new() -> Self {
EpollReadyEvents {
epollin: false,
epollout: false,
epollrdhup: false,
epollhup: false,
epollerr: false,
}
}
pub fn get_event_bitmask<'tcx>(&self, ecx: &MiriInterpCx<'tcx>) -> u32 {
let epollin = ecx.eval_libc_u32("EPOLLIN");
let epollout = ecx.eval_libc_u32("EPOLLOUT");
let epollrdhup = ecx.eval_libc_u32("EPOLLRDHUP");
let epollhup = ecx.eval_libc_u32("EPOLLHUP");
let epollerr = ecx.eval_libc_u32("EPOLLERR");
let mut bitmask = 0;
if self.epollin {
bitmask |= epollin;
}
if self.epollout {
bitmask |= epollout;
}
if self.epollrdhup {
bitmask |= epollrdhup;
}
if self.epollhup {
bitmask |= epollhup;
}
if self.epollerr {
bitmask |= epollerr;
}
bitmask
}
}
impl Epoll {
fn get_ready_list(&self) -> Rc<RefCell<BTreeMap<(FdId, i32), EpollEventInstance>>> {
Rc::clone(&self.ready_list)
}
}
impl FileDescription for Epoll {
@ -37,11 +132,51 @@ impl FileDescription for Epoll {
fn close<'tcx>(
self: Box<Self>,
_communicate_allowed: bool,
_ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, io::Result<()>> {
Ok(Ok(()))
}
}
/// The table of all EpollEventInterest.
/// The BTreeMap key is the FdId of an active file description registered with
/// any epoll instance. The value is a list of EpollEventInterest associated
/// with that file description.
pub struct EpollInterestTable(BTreeMap<FdId, Vec<Weak<RefCell<EpollEventInterest>>>>);
impl EpollInterestTable {
pub(crate) fn new() -> Self {
EpollInterestTable(BTreeMap::new())
}
pub fn insert_epoll_interest(&mut self, id: FdId, fd: Weak<RefCell<EpollEventInterest>>) {
match self.0.get_mut(&id) {
Some(fds) => {
fds.push(fd);
}
None => {
let vec = vec![fd];
self.0.insert(id, vec);
}
}
}
pub fn get_epoll_interest(&self, id: FdId) -> Option<&Vec<Weak<RefCell<EpollEventInterest>>>> {
self.0.get(&id)
}
pub fn get_epoll_interest_mut(
&mut self,
id: FdId,
) -> Option<&mut Vec<Weak<RefCell<EpollEventInterest>>>> {
self.0.get_mut(&id)
}
pub fn remove(&mut self, id: FdId) {
self.0.remove(&id);
}
}
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
/// This function returns a file descriptor referring to the new `Epoll` instance. This file
@ -64,6 +199,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
);
}
let mut epoll_instance = Epoll::default();
epoll_instance.ready_list = Rc::new(RefCell::new(BTreeMap::new()));
let fd = this.machine.fds.insert_new(Epoll::default());
Ok(Scalar::from_i32(fd))
}
@ -90,48 +228,156 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
) -> InterpResult<'tcx, Scalar> {
let this = self.eval_context_mut();
let epfd = this.read_scalar(epfd)?.to_i32()?;
let epfd_value = this.read_scalar(epfd)?.to_i32()?;
let op = this.read_scalar(op)?.to_i32()?;
let fd = this.read_scalar(fd)?.to_i32()?;
let _event = this.read_scalar(event)?.to_pointer(this)?;
let event = this.deref_pointer_as(event, this.libc_ty_layout("epoll_event"))?;
let epoll_ctl_add = this.eval_libc_i32("EPOLL_CTL_ADD");
let epoll_ctl_mod = this.eval_libc_i32("EPOLL_CTL_MOD");
let epoll_ctl_del = this.eval_libc_i32("EPOLL_CTL_DEL");
let epollin = this.eval_libc_u32("EPOLLIN");
let epollout = this.eval_libc_u32("EPOLLOUT");
let epollrdhup = this.eval_libc_u32("EPOLLRDHUP");
let epollet = this.eval_libc_u32("EPOLLET");
let epollhup = this.eval_libc_u32("EPOLLHUP");
let epollerr = this.eval_libc_u32("EPOLLERR");
// Fail on unsupported operations.
if op & epoll_ctl_add != epoll_ctl_add
&& op & epoll_ctl_mod != epoll_ctl_mod
&& op & epoll_ctl_del != epoll_ctl_del
{
throw_unsup_format!("epoll_ctl: encountered unknown unsupported operation {:#x}", op);
}
// Check if epfd is a valid epoll file descriptor.
let Some(epfd) = this.machine.fds.get(epfd_value) else {
return Ok(Scalar::from_i32(this.fd_not_found()?));
};
let epoll_file_description = epfd
.downcast::<Epoll>()
.ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_ctl`"))?;
let mut interest_list = epoll_file_description.interest_list.borrow_mut();
let ready_list = &epoll_file_description.ready_list;
let Some(file_descriptor) = this.machine.fds.get(fd) else {
return Ok(Scalar::from_i32(this.fd_not_found()?));
};
let id = file_descriptor.get_id();
if op == epoll_ctl_add || op == epoll_ctl_mod {
let event = this.deref_pointer_as(event, this.libc_ty_layout("epoll_event"))?;
// Read event bitmask and data from epoll_event passed by caller.
let mut events = this.read_scalar(&this.project_field(&event, 0)?)?.to_u32()?;
let data = this.read_scalar(&this.project_field(&event, 1)?)?.to_u64()?;
let events = this.project_field(&event, 0)?;
let events = this.read_scalar(&events)?.to_u32()?;
let data = this.project_field(&event, 1)?;
let data = this.read_scalar(&data)?;
let event = EpollEvent { events, data };
// Unset the flag we support to discover if any unsupported flags are used.
let mut flags = events;
// epoll_wait(2) will always wait for epollhup and epollerr; it is not
// necessary to set it in events when calling epoll_ctl().
// So we will always set these two event types.
events |= epollhup;
events |= epollerr;
let Some(mut epfd) = this.machine.fds.get_mut(epfd) else {
return Ok(Scalar::from_i32(this.fd_not_found()?));
};
let epfd = epfd
.downcast_mut::<Epoll>()
.ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_ctl`"))?;
if events & epollet != epollet {
// We only support edge-triggered notification for now.
throw_unsup_format!("epoll_ctl: epollet flag must be included.");
} else {
flags &= !epollet;
}
if flags & epollin == epollin {
flags &= !epollin;
}
if flags & epollout == epollout {
flags &= !epollout;
}
if flags & epollrdhup == epollrdhup {
flags &= !epollrdhup;
}
if flags & epollhup == epollhup {
flags &= !epollhup;
}
if flags & epollerr == epollerr {
flags &= !epollerr;
}
if flags != 0 {
throw_unsup_format!(
"epoll_ctl: encountered unknown unsupported flags {:#x}",
flags
);
}
epfd.file_descriptors.insert(fd, event);
Ok(Scalar::from_i32(0))
let epoll_key = (id, fd);
// Check the existence of fd in the interest list.
if op == epoll_ctl_add {
if interest_list.contains_key(&epoll_key) {
let eexist = this.eval_libc("EEXIST");
this.set_last_error(eexist)?;
return Ok(Scalar::from_i32(-1));
}
} else {
if !interest_list.contains_key(&epoll_key) {
let enoent = this.eval_libc("ENOENT");
this.set_last_error(enoent)?;
return Ok(Scalar::from_i32(-1));
}
}
let id = file_descriptor.get_id();
// Create an epoll_interest.
let interest = Rc::new(RefCell::new(EpollEventInterest {
file_descriptor: fd,
events,
data,
ready_list: Rc::clone(ready_list),
}));
if op == epoll_ctl_add {
// Insert an epoll_interest to global epoll_interest list.
this.machine.epoll_interests.insert_epoll_interest(id, Rc::downgrade(&interest));
interest_list.insert(epoll_key, interest);
} else {
// Directly modify the epoll_interest so the global epoll_event_interest table
// will be updated too.
let mut epoll_interest = interest_list.get_mut(&epoll_key).unwrap().borrow_mut();
epoll_interest.events = events;
epoll_interest.data = data;
}
// Readiness will be updated immediately when the epoll_event_interest is added or modified.
this.check_and_update_readiness(&file_descriptor)?;
return Ok(Scalar::from_i32(0));
} else if op == epoll_ctl_del {
let Some(mut epfd) = this.machine.fds.get_mut(epfd) else {
return Ok(Scalar::from_i32(this.fd_not_found()?));
};
let epfd = epfd
.downcast_mut::<Epoll>()
.ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_ctl`"))?;
let epoll_key = (id, fd);
epfd.file_descriptors.remove(&fd);
Ok(Scalar::from_i32(0))
} else {
let einval = this.eval_libc("EINVAL");
this.set_last_error(einval)?;
Ok(Scalar::from_i32(-1))
// Remove epoll_event_interest from interest_list.
let Some(epoll_interest) = interest_list.remove(&epoll_key) else {
let enoent = this.eval_libc("ENOENT");
this.set_last_error(enoent)?;
return Ok(Scalar::from_i32(-1));
};
// All related Weak<EpollEventInterest> will fail to upgrade after the drop.
drop(epoll_interest);
// Remove related epoll_interest from ready list.
ready_list.borrow_mut().remove(&epoll_key);
// Remove dangling EpollEventInterest from its global table.
// .unwrap() below should succeed because the file description id must have registered
// at least one epoll_interest, if not, it will fail when removing epoll_interest from
// interest list.
this.machine
.epoll_interests
.get_epoll_interest_mut(id)
.unwrap()
.retain(|event| event.upgrade().is_some());
return Ok(Scalar::from_i32(0));
}
Ok(Scalar::from_i32(-1))
}
/// The `epoll_wait()` system call waits for events on the `Epoll`
@ -166,25 +412,116 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
fn epoll_wait(
&mut self,
epfd: &OpTy<'tcx>,
events: &OpTy<'tcx>,
events_op: &OpTy<'tcx>,
maxevents: &OpTy<'tcx>,
timeout: &OpTy<'tcx>,
) -> InterpResult<'tcx, Scalar> {
let this = self.eval_context_mut();
let epfd = this.read_scalar(epfd)?.to_i32()?;
let _events = this.read_scalar(events)?.to_pointer(this)?;
let _maxevents = this.read_scalar(maxevents)?.to_i32()?;
let _timeout = this.read_scalar(timeout)?.to_i32()?;
let events = this.read_immediate(events_op)?;
let maxevents = this.read_scalar(maxevents)?.to_i32()?;
let timeout = this.read_scalar(timeout)?.to_i32()?;
let Some(mut epfd) = this.machine.fds.get_mut(epfd) else {
if epfd <= 0 || maxevents <= 0 {
let einval = this.eval_libc("EINVAL");
this.set_last_error(einval)?;
return Ok(Scalar::from_i32(-1));
}
// This needs to come after the maxevents value check, or else maxevents.try_into().unwrap()
// will fail.
let events = this.deref_pointer_as(
&events,
this.libc_array_ty_layout("epoll_event", maxevents.try_into().unwrap()),
)?;
// FIXME: Implement blocking support
if timeout != 0 {
throw_unsup_format!("epoll_wait: timeout value can only be 0");
}
let Some(epfd) = this.machine.fds.get(epfd) else {
return Ok(Scalar::from_i32(this.fd_not_found()?));
};
let _epfd = epfd
.downcast_mut::<Epoll>()
let epoll_file_description = epfd
.downcast::<Epoll>()
.ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_wait`"))?;
// FIXME return number of events ready when scheme for marking events ready exists
throw_unsup_format!("returning ready events from epoll_wait is not yet implemented");
let ready_list = epoll_file_description.get_ready_list();
let mut ready_list = ready_list.borrow_mut();
let mut num_of_events: i32 = 0;
let mut array_iter = this.project_array_fields(&events)?;
while let Some(des) = array_iter.next(this)? {
if let Some(epoll_event_instance) = ready_list_next(this, &mut ready_list) {
this.write_int_fields_named(
&[
("events", epoll_event_instance.events.into()),
("u64", epoll_event_instance.data.into()),
],
&des.1,
)?;
num_of_events = num_of_events.strict_add(1);
} else {
break;
}
}
Ok(Scalar::from_i32(num_of_events))
}
/// For a specific file description, get its ready events and update the corresponding ready
/// list. This function should be called whenever an event causes more bytes or an EOF to become
/// newly readable from an FD, and whenever more bytes can be written to an FD or no more future
/// writes are possible.
///
/// This *will* report an event if anyone is subscribed to it, without any further filtering, so
/// do not call this function when an FD didn't have anything happen to it!
fn check_and_update_readiness(&self, fd_ref: &FileDescriptionRef) -> InterpResult<'tcx, ()> {
let this = self.eval_context_ref();
let id = fd_ref.get_id();
// Get a list of EpollEventInterest that is associated to a specific file description.
if let Some(epoll_interests) = this.machine.epoll_interests.get_epoll_interest(id) {
let epoll_ready_events = fd_ref.get_epoll_ready_events()?;
// Get the bitmask of ready events.
let ready_events = epoll_ready_events.get_event_bitmask(this);
for weak_epoll_interest in epoll_interests {
if let Some(epoll_interest) = weak_epoll_interest.upgrade() {
// This checks if any of the events specified in epoll_event_interest.events
// match those in ready_events.
let epoll_event_interest = epoll_interest.borrow();
let flags = epoll_event_interest.events & ready_events;
// If there is any event that we are interested in being specified as ready,
// insert an epoll_return to the ready list.
if flags != 0 {
let epoll_key = (id, epoll_event_interest.file_descriptor);
let ready_list = &mut epoll_event_interest.ready_list.borrow_mut();
let event_instance =
EpollEventInstance::new(flags, epoll_event_interest.data);
ready_list.insert(epoll_key, event_instance);
}
}
}
}
Ok(())
}
}
/// This function takes in ready list and returns EpollEventInstance with file description
/// that is not closed.
fn ready_list_next(
ecx: &MiriInterpCx<'_>,
ready_list: &mut BTreeMap<(FdId, i32), EpollEventInstance>,
) -> Option<EpollEventInstance> {
while let Some((epoll_key, epoll_event_instance)) = ready_list.pop_first() {
// This ensures that we only return events that we are interested. The FD might have been closed since
// the event was generated, in which case we are not interested anymore.
// When a file description is fully closed, it gets removed from `machine.epoll_interests`,
// so we skip events whose FD is not in that map anymore.
if ecx.machine.epoll_interests.get_epoll_interest(epoll_key.0).is_some() {
return Some(epoll_event_instance);
}
}
return None;
}

View File

@ -1,10 +1,13 @@
//! Linux `eventfd` implementation.
use std::cell::{Cell, RefCell};
use std::io;
use std::io::{Error, ErrorKind};
use std::mem;
use rustc_target::abi::Endian;
use crate::shims::unix::fd::FileDescriptionRef;
use crate::shims::unix::linux::epoll::{EpollReadyEvents, EvalContextExt as _};
use crate::shims::unix::*;
use crate::{concurrency::VClock, *};
@ -25,9 +28,9 @@ const MAX_COUNTER: u64 = u64::MAX - 1;
struct Event {
/// The object contains an unsigned 64-bit integer (uint64_t) counter that is maintained by the
/// kernel. This counter is initialized with the value specified in the argument initval.
counter: u64,
counter: Cell<u64>,
is_nonblock: bool,
clock: VClock,
clock: RefCell<VClock>,
}
impl FileDescription for Event {
@ -35,16 +38,29 @@ impl FileDescription for Event {
"event"
}
fn get_epoll_ready_events<'tcx>(&self) -> InterpResult<'tcx, EpollReadyEvents> {
// We only check the status of EPOLLIN and EPOLLOUT flags for eventfd. If other event flags
// need to be supported in the future, the check should be added here.
Ok(EpollReadyEvents {
epollin: self.counter.get() != 0,
epollout: self.counter.get() != MAX_COUNTER,
..EpollReadyEvents::new()
})
}
fn close<'tcx>(
self: Box<Self>,
_communicate_allowed: bool,
_ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, io::Result<()>> {
Ok(Ok(()))
}
/// Read the counter in the buffer and return the counter if succeeded.
fn read<'tcx>(
&mut self,
&self,
self_ref: &FileDescriptionRef,
_communicate_allowed: bool,
bytes: &mut [u8],
ecx: &mut MiriInterpCx<'tcx>,
@ -54,7 +70,8 @@ impl FileDescription for Event {
return Ok(Err(Error::from(ErrorKind::InvalidInput)));
};
// Block when counter == 0.
if self.counter == 0 {
let counter = self.counter.get();
if counter == 0 {
if self.is_nonblock {
return Ok(Err(Error::from(ErrorKind::WouldBlock)));
} else {
@ -63,13 +80,17 @@ impl FileDescription for Event {
}
} else {
// Synchronize with all prior `write` calls to this FD.
ecx.acquire_clock(&self.clock);
ecx.acquire_clock(&self.clock.borrow());
// Return the counter in the host endianness using the buffer provided by caller.
*bytes = match ecx.tcx.sess.target.endian {
Endian::Little => self.counter.to_le_bytes(),
Endian::Big => self.counter.to_be_bytes(),
Endian::Little => counter.to_le_bytes(),
Endian::Big => counter.to_be_bytes(),
};
self.counter = 0;
self.counter.set(0);
// When any of the event happened, we check and update the status of all supported event
// types for current file description.
ecx.check_and_update_readiness(self_ref)?;
return Ok(Ok(U64_ARRAY_SIZE));
}
}
@ -87,7 +108,8 @@ impl FileDescription for Event {
/// supplied buffer is less than 8 bytes, or if an attempt is
/// made to write the value 0xffffffffffffffff.
fn write<'tcx>(
&mut self,
&self,
self_ref: &FileDescriptionRef,
_communicate_allowed: bool,
bytes: &[u8],
ecx: &mut MiriInterpCx<'tcx>,
@ -107,13 +129,13 @@ impl FileDescription for Event {
}
// If the addition does not let the counter to exceed the maximum value, update the counter.
// Else, block.
match self.counter.checked_add(num) {
match self.counter.get().checked_add(num) {
Some(new_count @ 0..=MAX_COUNTER) => {
// Future `read` calls will synchronize with this write, so update the FD clock.
if let Some(clock) = &ecx.release_clock() {
self.clock.join(clock);
self.clock.borrow_mut().join(clock);
}
self.counter = new_count;
self.counter.set(new_count);
}
None | Some(u64::MAX) => {
if self.is_nonblock {
@ -124,6 +146,10 @@ impl FileDescription for Event {
}
}
};
// When any of the event happened, we check and update the status of all supported event
// types for current file description.
ecx.check_and_update_readiness(self_ref)?;
Ok(Ok(U64_ARRAY_SIZE))
}
}
@ -178,11 +204,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
throw_unsup_format!("eventfd: encountered unknown unsupported flags {:#x}", flags);
}
let fd = this.machine.fds.insert_new(Event {
counter: val.into(),
let fds = &mut this.machine.fds;
let fd_value = fds.insert_new(Event {
counter: Cell::new(val.into()),
is_nonblock,
clock: VClock::default(),
clock: RefCell::new(VClock::default()),
});
Ok(Scalar::from_i32(fd))
Ok(Scalar::from_i32(fd_value))
}
}

View File

@ -4,9 +4,9 @@ mod env;
mod fd;
mod fs;
mod mem;
mod socket;
mod sync;
mod thread;
mod unnamed_socket;
mod android;
mod freebsd;
@ -17,14 +17,15 @@ mod solarish;
pub use env::UnixEnvVars;
pub use fd::{FdTable, FileDescription};
pub use fs::DirTable;
pub use linux::epoll::EpollInterestTable;
// All the Unix-specific extension traits
pub use env::EvalContextExt as _;
pub use fd::EvalContextExt as _;
pub use fs::EvalContextExt as _;
pub use mem::EvalContextExt as _;
pub use socket::EvalContextExt as _;
pub use sync::EvalContextExt as _;
pub use thread::EvalContextExt as _;
pub use unnamed_socket::EvalContextExt as _;
// Make up some constants.
const UID: u32 = 1000;

View File

@ -1,232 +0,0 @@
use std::cell::RefCell;
use std::collections::VecDeque;
use std::io;
use std::io::{Error, ErrorKind, Read};
use std::rc::{Rc, Weak};
use crate::shims::unix::*;
use crate::{concurrency::VClock, *};
/// The maximum capacity of the socketpair buffer in bytes.
/// This number is arbitrary as the value can always
/// be configured in the real system.
const MAX_SOCKETPAIR_BUFFER_CAPACITY: usize = 212992;
/// Pair of connected sockets.
#[derive(Debug)]
struct SocketPair {
// By making the write link weak, a `write` can detect when all readers are
// gone, and trigger EPIPE as appropriate.
writebuf: Weak<RefCell<Buffer>>,
readbuf: Rc<RefCell<Buffer>>,
is_nonblock: bool,
}
#[derive(Debug)]
struct Buffer {
buf: VecDeque<u8>,
clock: VClock,
/// Indicates if there is at least one active writer to this buffer.
/// If all writers of this buffer are dropped, buf_has_writer becomes false and we
/// indicate EOF instead of blocking.
buf_has_writer: bool,
}
impl FileDescription for SocketPair {
fn name(&self) -> &'static str {
"socketpair"
}
fn close<'tcx>(
self: Box<Self>,
_communicate_allowed: bool,
) -> InterpResult<'tcx, io::Result<()>> {
// This is used to signal socketfd of other side that there is no writer to its readbuf.
// If the upgrade fails, there is no need to update as all read ends have been dropped.
if let Some(writebuf) = self.writebuf.upgrade() {
writebuf.borrow_mut().buf_has_writer = false;
};
Ok(Ok(()))
}
fn read<'tcx>(
&mut self,
_communicate_allowed: bool,
bytes: &mut [u8],
ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, io::Result<usize>> {
let request_byte_size = bytes.len();
let mut readbuf = self.readbuf.borrow_mut();
// Always succeed on read size 0.
if request_byte_size == 0 {
return Ok(Ok(0));
}
if readbuf.buf.is_empty() {
if !readbuf.buf_has_writer {
// Socketpair with no writer and empty buffer.
// 0 bytes successfully read indicates end-of-file.
return Ok(Ok(0));
} else {
if self.is_nonblock {
// Non-blocking socketpair with writer and empty buffer.
// https://linux.die.net/man/2/read
// EAGAIN or EWOULDBLOCK can be returned for socket,
// POSIX.1-2001 allows either error to be returned for this case.
// Since there is no ErrorKind for EAGAIN, WouldBlock is used.
return Ok(Err(Error::from(ErrorKind::WouldBlock)));
} else {
// Blocking socketpair with writer and empty buffer.
// FIXME: blocking is currently not supported
throw_unsup_format!("socketpair read: blocking isn't supported yet");
}
}
}
// Synchronize with all previous writes to this buffer.
// FIXME: this over-synchronizes; a more precise approach would be to
// only sync with the writes whose data we will read.
ecx.acquire_clock(&readbuf.clock);
// Do full read / partial read based on the space available.
// Conveniently, `read` exists on `VecDeque` and has exactly the desired behavior.
let actual_read_size = readbuf.buf.read(bytes).unwrap();
return Ok(Ok(actual_read_size));
}
fn write<'tcx>(
&mut self,
_communicate_allowed: bool,
bytes: &[u8],
ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, io::Result<usize>> {
let write_size = bytes.len();
// Always succeed on write size 0.
// ("If count is zero and fd refers to a file other than a regular file, the results are not specified.")
if write_size == 0 {
return Ok(Ok(0));
}
let Some(writebuf) = self.writebuf.upgrade() else {
// If the upgrade from Weak to Rc fails, it indicates that all read ends have been
// closed.
return Ok(Err(Error::from(ErrorKind::BrokenPipe)));
};
let mut writebuf = writebuf.borrow_mut();
let data_size = writebuf.buf.len();
let available_space = MAX_SOCKETPAIR_BUFFER_CAPACITY.strict_sub(data_size);
if available_space == 0 {
if self.is_nonblock {
// Non-blocking socketpair with a full buffer.
return Ok(Err(Error::from(ErrorKind::WouldBlock)));
} else {
// Blocking socketpair with a full buffer.
throw_unsup_format!("socketpair write: blocking isn't supported yet");
}
}
// Remember this clock so `read` can synchronize with us.
if let Some(clock) = &ecx.release_clock() {
writebuf.clock.join(clock);
}
// Do full write / partial write based on the space available.
let actual_write_size = write_size.min(available_space);
writebuf.buf.extend(&bytes[..actual_write_size]);
return Ok(Ok(actual_write_size));
}
}
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
/// For more information on the arguments see the socketpair manpage:
/// <https://linux.die.net/man/2/socketpair>
fn socketpair(
&mut self,
domain: &OpTy<'tcx>,
type_: &OpTy<'tcx>,
protocol: &OpTy<'tcx>,
sv: &OpTy<'tcx>,
) -> InterpResult<'tcx, Scalar> {
let this = self.eval_context_mut();
let domain = this.read_scalar(domain)?.to_i32()?;
let mut type_ = this.read_scalar(type_)?.to_i32()?;
let protocol = this.read_scalar(protocol)?.to_i32()?;
let sv = this.deref_pointer(sv)?;
let mut is_sock_nonblock = false;
// Parse and remove the type flags that we support. If type != 0 after removing,
// unsupported flags are used.
if type_ & this.eval_libc_i32("SOCK_STREAM") == this.eval_libc_i32("SOCK_STREAM") {
type_ &= !(this.eval_libc_i32("SOCK_STREAM"));
}
// SOCK_NONBLOCK only exists on Linux.
if this.tcx.sess.target.os == "linux" {
if type_ & this.eval_libc_i32("SOCK_NONBLOCK") == this.eval_libc_i32("SOCK_NONBLOCK") {
is_sock_nonblock = true;
type_ &= !(this.eval_libc_i32("SOCK_NONBLOCK"));
}
if type_ & this.eval_libc_i32("SOCK_CLOEXEC") == this.eval_libc_i32("SOCK_CLOEXEC") {
type_ &= !(this.eval_libc_i32("SOCK_CLOEXEC"));
}
}
// Fail on unsupported input.
// AF_UNIX and AF_LOCAL are synonyms, so we accept both in case
// their values differ.
if domain != this.eval_libc_i32("AF_UNIX") && domain != this.eval_libc_i32("AF_LOCAL") {
throw_unsup_format!(
"socketpair: domain {:#x} is unsupported, only AF_UNIX \
and AF_LOCAL are allowed",
domain
);
} else if type_ != 0 {
throw_unsup_format!(
"socketpair: type {:#x} is unsupported, only SOCK_STREAM, \
SOCK_CLOEXEC and SOCK_NONBLOCK are allowed",
type_
);
} else if protocol != 0 {
throw_unsup_format!(
"socketpair: socket protocol {protocol} is unsupported, \
only 0 is allowed",
);
}
let buffer1 = Rc::new(RefCell::new(Buffer {
buf: VecDeque::new(),
clock: VClock::default(),
buf_has_writer: true,
}));
let buffer2 = Rc::new(RefCell::new(Buffer {
buf: VecDeque::new(),
clock: VClock::default(),
buf_has_writer: true,
}));
let socketpair_0 = SocketPair {
writebuf: Rc::downgrade(&buffer1),
readbuf: Rc::clone(&buffer2),
is_nonblock: is_sock_nonblock,
};
let socketpair_1 = SocketPair {
writebuf: Rc::downgrade(&buffer2),
readbuf: Rc::clone(&buffer1),
is_nonblock: is_sock_nonblock,
};
let fds = &mut this.machine.fds;
let sv0 = fds.insert_new(socketpair_0);
let sv1 = fds.insert_new(socketpair_1);
let sv0 = Scalar::from_int(sv0, sv.layout.size);
let sv1 = Scalar::from_int(sv1, sv.layout.size);
this.write_scalar(sv0, &sv)?;
this.write_scalar(sv1, &sv.offset(sv.layout.size, sv.layout, this)?)?;
Ok(Scalar::from_i32(0))
}
}

View File

@ -0,0 +1,386 @@
//! This implements "anonymous" sockets, that do not correspond to anything on the host system and
//! are entirely implemented inside Miri.
//! We also use the same infrastructure to implement unnamed pipes.
use std::cell::{Cell, OnceCell, RefCell};
use std::collections::VecDeque;
use std::io;
use std::io::{Error, ErrorKind, Read};
use crate::shims::unix::fd::{FileDescriptionRef, WeakFileDescriptionRef};
use crate::shims::unix::linux::epoll::{EpollReadyEvents, EvalContextExt as _};
use crate::shims::unix::*;
use crate::{concurrency::VClock, *};
/// The maximum capacity of the socketpair buffer in bytes.
/// This number is arbitrary as the value can always
/// be configured in the real system.
const MAX_SOCKETPAIR_BUFFER_CAPACITY: usize = 212992;
/// One end of a pair of connected unnamed sockets.
#[derive(Debug)]
struct AnonSocket {
/// The buffer we are reading from, or `None` if this is the writing end of a pipe.
/// (In that case, the peer FD will be the reading end of that pipe.)
readbuf: Option<RefCell<Buffer>>,
/// The `AnonSocket` file descriptor that is our "peer", and that holds the buffer we are
/// writing to. This is a weak reference because the other side may be closed before us; all
/// future writes will then trigger EPIPE.
peer_fd: OnceCell<WeakFileDescriptionRef>,
/// Indicates whether the peer has lost data when the file description is closed.
/// This flag is set to `true` if the peer's `readbuf` is non-empty at the time
/// of closure.
peer_lost_data: Cell<bool>,
is_nonblock: bool,
}
#[derive(Debug)]
struct Buffer {
buf: VecDeque<u8>,
clock: VClock,
}
impl Buffer {
fn new() -> Self {
Buffer { buf: VecDeque::new(), clock: VClock::default() }
}
}
impl AnonSocket {
fn peer_fd(&self) -> &WeakFileDescriptionRef {
self.peer_fd.get().unwrap()
}
}
impl FileDescription for AnonSocket {
fn name(&self) -> &'static str {
"socketpair"
}
fn get_epoll_ready_events<'tcx>(&self) -> InterpResult<'tcx, EpollReadyEvents> {
// We only check the status of EPOLLIN, EPOLLOUT, EPOLLHUP and EPOLLRDHUP flags.
// If other event flags need to be supported in the future, the check should be added here.
let mut epoll_ready_events = EpollReadyEvents::new();
// Check if it is readable.
if let Some(readbuf) = &self.readbuf {
if !readbuf.borrow().buf.is_empty() {
epoll_ready_events.epollin = true;
}
} else {
// Without a read buffer, reading never blocks, so we are always ready.
epoll_ready_events.epollin = true;
}
// Check if is writable.
if let Some(peer_fd) = self.peer_fd().upgrade() {
if let Some(writebuf) = &peer_fd.downcast::<AnonSocket>().unwrap().readbuf {
let data_size = writebuf.borrow().buf.len();
let available_space = MAX_SOCKETPAIR_BUFFER_CAPACITY.strict_sub(data_size);
if available_space != 0 {
epoll_ready_events.epollout = true;
}
} else {
// Without a write buffer, writing never blocks.
epoll_ready_events.epollout = true;
}
} else {
// Peer FD has been closed. This always sets both the RDHUP and HUP flags
// as we do not support `shutdown` that could be used to partially close the stream.
epoll_ready_events.epollrdhup = true;
epoll_ready_events.epollhup = true;
// Since the peer is closed, even if no data is available reads will return EOF and
// writes will return EPIPE. In other words, they won't block, so we mark this as ready
// for read and write.
epoll_ready_events.epollin = true;
epoll_ready_events.epollout = true;
// If there is data lost in peer_fd, set EPOLLERR.
if self.peer_lost_data.get() {
epoll_ready_events.epollerr = true;
}
}
Ok(epoll_ready_events)
}
fn close<'tcx>(
self: Box<Self>,
_communicate_allowed: bool,
ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, io::Result<()>> {
if let Some(peer_fd) = self.peer_fd().upgrade() {
// If the current readbuf is non-empty when the file description is closed,
// notify the peer that data lost has happened in current file description.
if let Some(readbuf) = &self.readbuf {
if !readbuf.borrow().buf.is_empty() {
peer_fd.downcast::<AnonSocket>().unwrap().peer_lost_data.set(true);
}
}
// Notify peer fd that close has happened, since that can unblock reads and writes.
ecx.check_and_update_readiness(&peer_fd)?;
}
Ok(Ok(()))
}
fn read<'tcx>(
&self,
_self_ref: &FileDescriptionRef,
_communicate_allowed: bool,
bytes: &mut [u8],
ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, io::Result<usize>> {
let request_byte_size = bytes.len();
// Always succeed on read size 0.
if request_byte_size == 0 {
return Ok(Ok(0));
}
let Some(readbuf) = &self.readbuf else {
// FIXME: This should return EBADF, but there's no nice way to do that as there's no
// corresponding ErrorKind variant.
throw_unsup_format!("reading from the write end of a pipe");
};
let mut readbuf = readbuf.borrow_mut();
if readbuf.buf.is_empty() {
if self.peer_fd().upgrade().is_none() {
// Socketpair with no peer and empty buffer.
// 0 bytes successfully read indicates end-of-file.
return Ok(Ok(0));
} else {
if self.is_nonblock {
// Non-blocking socketpair with writer and empty buffer.
// https://linux.die.net/man/2/read
// EAGAIN or EWOULDBLOCK can be returned for socket,
// POSIX.1-2001 allows either error to be returned for this case.
// Since there is no ErrorKind for EAGAIN, WouldBlock is used.
return Ok(Err(Error::from(ErrorKind::WouldBlock)));
} else {
// Blocking socketpair with writer and empty buffer.
// FIXME: blocking is currently not supported
throw_unsup_format!("socketpair read: blocking isn't supported yet");
}
}
}
// Synchronize with all previous writes to this buffer.
// FIXME: this over-synchronizes; a more precise approach would be to
// only sync with the writes whose data we will read.
ecx.acquire_clock(&readbuf.clock);
// Do full read / partial read based on the space available.
// Conveniently, `read` exists on `VecDeque` and has exactly the desired behavior.
let actual_read_size = readbuf.buf.read(bytes).unwrap();
// Need to drop before others can access the readbuf again.
drop(readbuf);
// A notification should be provided for the peer file description even when it can
// only write 1 byte. This implementation is not compliant with the actual Linux kernel
// implementation. For optimization reasons, the kernel will only mark the file description
// as "writable" when it can write more than a certain number of bytes. Since we
// don't know what that *certain number* is, we will provide a notification every time
// a read is successful. This might result in our epoll emulation providing more
// notifications than the real system.
if let Some(peer_fd) = self.peer_fd().upgrade() {
ecx.check_and_update_readiness(&peer_fd)?;
}
return Ok(Ok(actual_read_size));
}
fn write<'tcx>(
&self,
_self_ref: &FileDescriptionRef,
_communicate_allowed: bool,
bytes: &[u8],
ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, io::Result<usize>> {
let write_size = bytes.len();
// Always succeed on write size 0.
// ("If count is zero and fd refers to a file other than a regular file, the results are not specified.")
if write_size == 0 {
return Ok(Ok(0));
}
// We are writing to our peer's readbuf.
let Some(peer_fd) = self.peer_fd().upgrade() else {
// If the upgrade from Weak to Rc fails, it indicates that all read ends have been
// closed.
return Ok(Err(Error::from(ErrorKind::BrokenPipe)));
};
let Some(writebuf) = &peer_fd.downcast::<AnonSocket>().unwrap().readbuf else {
// FIXME: This should return EBADF, but there's no nice way to do that as there's no
// corresponding ErrorKind variant.
throw_unsup_format!("writing to the reading end of a pipe");
};
let mut writebuf = writebuf.borrow_mut();
let data_size = writebuf.buf.len();
let available_space = MAX_SOCKETPAIR_BUFFER_CAPACITY.strict_sub(data_size);
if available_space == 0 {
if self.is_nonblock {
// Non-blocking socketpair with a full buffer.
return Ok(Err(Error::from(ErrorKind::WouldBlock)));
} else {
// Blocking socketpair with a full buffer.
throw_unsup_format!("socketpair write: blocking isn't supported yet");
}
}
// Remember this clock so `read` can synchronize with us.
if let Some(clock) = &ecx.release_clock() {
writebuf.clock.join(clock);
}
// Do full write / partial write based on the space available.
let actual_write_size = write_size.min(available_space);
writebuf.buf.extend(&bytes[..actual_write_size]);
// Need to stop accessing peer_fd so that it can be notified.
drop(writebuf);
// Notification should be provided for peer fd as it became readable.
// The kernel does this even if the fd was already readable before, so we follow suit.
ecx.check_and_update_readiness(&peer_fd)?;
return Ok(Ok(actual_write_size));
}
}
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
/// For more information on the arguments see the socketpair manpage:
/// <https://linux.die.net/man/2/socketpair>
fn socketpair(
&mut self,
domain: &OpTy<'tcx>,
type_: &OpTy<'tcx>,
protocol: &OpTy<'tcx>,
sv: &OpTy<'tcx>,
) -> InterpResult<'tcx, Scalar> {
let this = self.eval_context_mut();
let domain = this.read_scalar(domain)?.to_i32()?;
let mut type_ = this.read_scalar(type_)?.to_i32()?;
let protocol = this.read_scalar(protocol)?.to_i32()?;
let sv = this.deref_pointer(sv)?;
let mut is_sock_nonblock = false;
// Parse and remove the type flags that we support.
// SOCK_NONBLOCK only exists on Linux.
if this.tcx.sess.target.os == "linux" {
if type_ & this.eval_libc_i32("SOCK_NONBLOCK") == this.eval_libc_i32("SOCK_NONBLOCK") {
is_sock_nonblock = true;
type_ &= !(this.eval_libc_i32("SOCK_NONBLOCK"));
}
if type_ & this.eval_libc_i32("SOCK_CLOEXEC") == this.eval_libc_i32("SOCK_CLOEXEC") {
type_ &= !(this.eval_libc_i32("SOCK_CLOEXEC"));
}
}
// Fail on unsupported input.
// AF_UNIX and AF_LOCAL are synonyms, so we accept both in case
// their values differ.
if domain != this.eval_libc_i32("AF_UNIX") && domain != this.eval_libc_i32("AF_LOCAL") {
throw_unsup_format!(
"socketpair: domain {:#x} is unsupported, only AF_UNIX \
and AF_LOCAL are allowed",
domain
);
} else if type_ != this.eval_libc_i32("SOCK_STREAM") {
throw_unsup_format!(
"socketpair: type {:#x} is unsupported, only SOCK_STREAM, \
SOCK_CLOEXEC and SOCK_NONBLOCK are allowed",
type_
);
} else if protocol != 0 {
throw_unsup_format!(
"socketpair: socket protocol {protocol} is unsupported, \
only 0 is allowed",
);
}
// Generate file descriptions.
let fds = &mut this.machine.fds;
let fd0 = fds.new_ref(AnonSocket {
readbuf: Some(RefCell::new(Buffer::new())),
peer_fd: OnceCell::new(),
peer_lost_data: Cell::new(false),
is_nonblock: is_sock_nonblock,
});
let fd1 = fds.new_ref(AnonSocket {
readbuf: Some(RefCell::new(Buffer::new())),
peer_fd: OnceCell::new(),
peer_lost_data: Cell::new(false),
is_nonblock: is_sock_nonblock,
});
// Make the file descriptions point to each other.
fd0.downcast::<AnonSocket>().unwrap().peer_fd.set(fd1.downgrade()).unwrap();
fd1.downcast::<AnonSocket>().unwrap().peer_fd.set(fd0.downgrade()).unwrap();
// Insert the file description to the fd table, generating the file descriptors.
let sv0 = fds.insert(fd0);
let sv1 = fds.insert(fd1);
// Return socketpair file descriptors to the caller.
let sv0 = Scalar::from_int(sv0, sv.layout.size);
let sv1 = Scalar::from_int(sv1, sv.layout.size);
this.write_scalar(sv0, &sv)?;
this.write_scalar(sv1, &sv.offset(sv.layout.size, sv.layout, this)?)?;
Ok(Scalar::from_i32(0))
}
fn pipe2(
&mut self,
pipefd: &OpTy<'tcx>,
flags: Option<&OpTy<'tcx>>,
) -> InterpResult<'tcx, Scalar> {
let this = self.eval_context_mut();
let pipefd = this.deref_pointer(pipefd)?;
let flags = match flags {
Some(flags) => this.read_scalar(flags)?.to_i32()?,
None => 0,
};
// As usual we ignore CLOEXEC.
let cloexec = this.eval_libc_i32("O_CLOEXEC");
if flags != 0 && flags != cloexec {
throw_unsup_format!("unsupported flags in `pipe2`");
}
// Generate file descriptions.
// pipefd[0] refers to the read end of the pipe.
let fds = &mut this.machine.fds;
let fd0 = fds.new_ref(AnonSocket {
readbuf: Some(RefCell::new(Buffer::new())),
peer_fd: OnceCell::new(),
peer_lost_data: Cell::new(false),
is_nonblock: false,
});
let fd1 = fds.new_ref(AnonSocket {
readbuf: None,
peer_fd: OnceCell::new(),
peer_lost_data: Cell::new(false),
is_nonblock: false,
});
// Make the file descriptions point to each other.
fd0.downcast::<AnonSocket>().unwrap().peer_fd.set(fd1.downgrade()).unwrap();
fd1.downcast::<AnonSocket>().unwrap().peer_fd.set(fd0.downgrade()).unwrap();
// Insert the file description to the fd table, generating the file descriptors.
let pipefd0 = fds.insert(fd0);
let pipefd1 = fds.insert(fd1);
// Return file descriptors to the caller.
let pipefd0 = Scalar::from_int(pipefd0, pipefd.layout.size);
let pipefd1 = Scalar::from_int(pipefd1, pipefd.layout.size);
this.write_scalar(pipefd0, &pipefd)?;
this.write_scalar(pipefd1, &pipefd.offset(pipefd.layout.size, pipefd.layout, this)?)?;
Ok(Scalar::from_i32(0))
}
}

View File

@ -73,13 +73,12 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
round_all::<rustc_apfloat::ieee::Double>(this, op, rounding, dest)?;
}
// Used to implement _mm256_{sqrt,rcp,rsqrt}_ps functions.
// Used to implement _mm256_{rcp,rsqrt}_ps functions.
// Performs the operations on all components of `op`.
"sqrt.ps.256" | "rcp.ps.256" | "rsqrt.ps.256" => {
"rcp.ps.256" | "rsqrt.ps.256" => {
let [op] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let which = match unprefixed_name {
"sqrt.ps.256" => FloatUnaryOp::Sqrt,
"rcp.ps.256" => FloatUnaryOp::Rcp,
"rsqrt.ps.256" => FloatUnaryOp::Rsqrt,
_ => unreachable!(),

View File

@ -159,8 +159,6 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
#[derive(Copy, Clone)]
enum FloatBinOp {
/// Arithmetic operation
Arith(mir::BinOp),
/// Comparison
///
/// The semantics of this operator is a case distinction: we compare the two operands,
@ -247,16 +245,11 @@ impl FloatBinOp {
/// Performs `which` scalar operation on `left` and `right` and returns
/// the result.
fn bin_op_float<'tcx, F: rustc_apfloat::Float>(
this: &crate::MiriInterpCx<'tcx>,
which: FloatBinOp,
left: &ImmTy<'tcx>,
right: &ImmTy<'tcx>,
) -> InterpResult<'tcx, Scalar> {
match which {
FloatBinOp::Arith(which) => {
let res = this.binary_op(which, left, right)?;
Ok(res.to_scalar())
}
FloatBinOp::Cmp { gt, lt, eq, unord } => {
let left = left.to_scalar().to_float::<F>()?;
let right = right.to_scalar().to_float::<F>()?;
@ -323,7 +316,6 @@ fn bin_op_simd_float_first<'tcx, F: rustc_apfloat::Float>(
assert_eq!(dest_len, right_len);
let res0 = bin_op_float::<F>(
this,
which,
&this.read_immediate(&this.project_index(&left, 0)?)?,
&this.read_immediate(&this.project_index(&right, 0)?)?,
@ -358,7 +350,7 @@ fn bin_op_simd_float_all<'tcx, F: rustc_apfloat::Float>(
let right = this.read_immediate(&this.project_index(&right, i)?)?;
let dest = this.project_index(&dest, i)?;
let res = bin_op_float::<F>(this, which, &left, &right)?;
let res = bin_op_float::<F>(which, &left, &right)?;
this.write_scalar(res, &dest)?;
}
@ -367,11 +359,6 @@ fn bin_op_simd_float_all<'tcx, F: rustc_apfloat::Float>(
#[derive(Copy, Clone)]
enum FloatUnaryOp {
/// sqrt(x)
///
/// <https://www.felixcloutier.com/x86/sqrtss>
/// <https://www.felixcloutier.com/x86/sqrtps>
Sqrt,
/// Approximation of 1/x
///
/// <https://www.felixcloutier.com/x86/rcpss>
@ -392,11 +379,6 @@ fn unary_op_f32<'tcx>(
op: &ImmTy<'tcx>,
) -> InterpResult<'tcx, Scalar> {
match which {
FloatUnaryOp::Sqrt => {
let op = op.to_scalar();
// FIXME using host floats
Ok(Scalar::from_u32(f32::from_bits(op.to_u32()?).sqrt().to_bits()))
}
FloatUnaryOp::Rcp => {
let op = op.to_scalar().to_f32()?;
let div = (Single::from_u128(1).value / op).value;

View File

@ -1,5 +1,4 @@
use rustc_apfloat::ieee::Single;
use rustc_middle::mir;
use rustc_span::Symbol;
use rustc_target::spec::abi::Abi;
@ -29,18 +28,14 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// performed only on the first element, copying the remaining elements from the input
// vector (for binary operations, from the left-hand side).
match unprefixed_name {
// Used to implement _mm_{add,sub,mul,div,min,max}_ss functions.
// Used to implement _mm_{min,max}_ss functions.
// Performs the operations on the first component of `left` and
// `right` and copies the remaining components from `left`.
"add.ss" | "sub.ss" | "mul.ss" | "div.ss" | "min.ss" | "max.ss" => {
"min.ss" | "max.ss" => {
let [left, right] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let which = match unprefixed_name {
"add.ss" => FloatBinOp::Arith(mir::BinOp::Add),
"sub.ss" => FloatBinOp::Arith(mir::BinOp::Sub),
"mul.ss" => FloatBinOp::Arith(mir::BinOp::Mul),
"div.ss" => FloatBinOp::Arith(mir::BinOp::Div),
"min.ss" => FloatBinOp::Min,
"max.ss" => FloatBinOp::Max,
_ => unreachable!(),
@ -65,14 +60,13 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
bin_op_simd_float_all::<Single>(this, which, left, right, dest)?;
}
// Used to implement _mm_{sqrt,rcp,rsqrt}_ss functions.
// Used to implement _mm_{rcp,rsqrt}_ss functions.
// Performs the operations on the first component of `op` and
// copies the remaining components from `op`.
"sqrt.ss" | "rcp.ss" | "rsqrt.ss" => {
"rcp.ss" | "rsqrt.ss" => {
let [op] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let which = match unprefixed_name {
"sqrt.ss" => FloatUnaryOp::Sqrt,
"rcp.ss" => FloatUnaryOp::Rcp,
"rsqrt.ss" => FloatUnaryOp::Rsqrt,
_ => unreachable!(),
@ -82,11 +76,10 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
}
// Used to implement _mm_{sqrt,rcp,rsqrt}_ps functions.
// Performs the operations on all components of `op`.
"sqrt.ps" | "rcp.ps" | "rsqrt.ps" => {
"rcp.ps" | "rsqrt.ps" => {
let [op] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let which = match unprefixed_name {
"sqrt.ps" => FloatUnaryOp::Sqrt,
"rcp.ps" => FloatUnaryOp::Rcp,
"rsqrt.ps" => FloatUnaryOp::Rsqrt,
_ => unreachable!(),

View File

@ -227,46 +227,6 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
bin_op_simd_float_all::<Double>(this, which, left, right, dest)?;
}
// Used to implement _mm_sqrt_sd functions.
// Performs the operations on the first component of `op` and
// copies the remaining components from `op`.
"sqrt.sd" => {
let [op] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let (op, op_len) = this.operand_to_simd(op)?;
let (dest, dest_len) = this.mplace_to_simd(dest)?;
assert_eq!(dest_len, op_len);
let op0 = this.read_scalar(&this.project_index(&op, 0)?)?.to_u64()?;
// FIXME using host floats
let res0 = Scalar::from_u64(f64::from_bits(op0).sqrt().to_bits());
this.write_scalar(res0, &this.project_index(&dest, 0)?)?;
for i in 1..dest_len {
this.copy_op(&this.project_index(&op, i)?, &this.project_index(&dest, i)?)?;
}
}
// Used to implement _mm_sqrt_pd functions.
// Performs the operations on all components of `op`.
"sqrt.pd" => {
let [op] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let (op, op_len) = this.operand_to_simd(op)?;
let (dest, dest_len) = this.mplace_to_simd(dest)?;
assert_eq!(dest_len, op_len);
for i in 0..dest_len {
let op = this.read_scalar(&this.project_index(&op, i)?)?.to_u64()?;
let dest = this.project_index(&dest, i)?;
// FIXME using host floats
let res = Scalar::from_u64(f64::from_bits(op).sqrt().to_bits());
this.write_scalar(res, &dest)?;
}
}
// Used to implement the _mm_cmp*_sd functions.
// Performs a comparison operation on the first component of `left`
// and `right`, returning 0 if false or `u64::MAX` if true. The remaining

View File

@ -1,6 +1,6 @@
//@compile-flags: -Zmiri-permissive-provenance -Zmiri-backtrace=full
//@only-target-x86_64-unknown-linux: support for tokio only on linux and x86
//@error-in-other-file: returning ready events from epoll_wait is not yet implemented
//@error-in-other-file: timeout value can only be 0
//@normalize-stderr-test: " += note:.*\n" -> ""
use tokio::time::{sleep, Duration, Instant};

View File

@ -1,4 +1,4 @@
error: unsupported operation: returning ready events from epoll_wait is not yet implemented
error: unsupported operation: epoll_wait: timeout value can only be 0
--> CARGO_REGISTRY/.../epoll.rs:LL:CC
|
LL | / syscall!(epoll_wait(
@ -7,7 +7,7 @@ LL | | events.as_mut_ptr(),
LL | | events.capacity() as i32,
LL | | timeout,
LL | | ))
| |__________^ returning ready events from epoll_wait is not yet implemented
| |__________^ epoll_wait: timeout value can only be 0
|
= help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support

View File

@ -0,0 +1,14 @@
// Previously, attempting to allocate with an alignment greater than 2^29 would cause miri to ICE
// because rustc does not support alignments that large.
// https://github.com/rust-lang/miri/issues/3687
extern "Rust" {
fn __rust_alloc(size: usize, align: usize) -> *mut u8;
}
fn main() {
unsafe {
__rust_alloc(1, 1 << 30);
//~^ERROR: exceeding rustc's maximum supported value
}
}

View File

@ -0,0 +1,14 @@
error: unsupported operation: creating allocation with alignment ALIGN exceeding rustc's maximum supported value
--> $DIR/unsupported_big_alignment.rs:LL:CC
|
LL | __rust_alloc(1, 1 << 30);
| ^^^^^^^^^^^^^^^^^^^^^^^^ creating allocation with alignment ALIGN exceeding rustc's maximum supported value
|
= help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support
= note: BACKTRACE:
= note: inside `main` at $DIR/unsupported_big_alignment.rs:LL:CC
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to 1 previous error

View File

@ -0,0 +1,11 @@
// Test non-power-of-two alignment.
extern "Rust" {
fn __rust_alloc(size: usize, align: usize) -> *mut u8;
}
fn main() {
unsafe {
__rust_alloc(1, 3);
//~^ERROR: creating allocation with non-power-of-two alignment
}
}

View File

@ -0,0 +1,15 @@
error: Undefined Behavior: creating allocation with non-power-of-two alignment ALIGN
--> $DIR/unsupported_non_power_two_alignment.rs:LL:CC
|
LL | __rust_alloc(1, 3);
| ^^^^^^^^^^^^^^^^^^ creating allocation with non-power-of-two 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 `main` at $DIR/unsupported_non_power_two_alignment.rs:LL:CC
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to 1 previous error

View File

@ -1,8 +1,8 @@
error: Undefined Behavior: not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected because it is an argument of call ID
error: Undefined Behavior: not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected
--> $DIR/aliasing_mut1.rs:LL:CC
|
LL | pub fn safe(x: &mut i32, y: &mut i32) {
| ^ not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected because it is an argument of call ID
| ^ not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected
|
= 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

View File

@ -1,8 +1,8 @@
error: Undefined Behavior: not granting access to tag <TAG> because that would remove [SharedReadOnly for <TAG>] which is strongly protected because it is an argument of call ID
error: Undefined Behavior: not granting access to tag <TAG> because that would remove [SharedReadOnly for <TAG>] which is strongly protected
--> $DIR/aliasing_mut2.rs:LL:CC
|
LL | pub fn safe(x: &i32, y: &mut i32) {
| ^ not granting access to tag <TAG> because that would remove [SharedReadOnly for <TAG>] which is strongly protected because it is an argument of call ID
| ^ not granting access to tag <TAG> because that would remove [SharedReadOnly for <TAG>] which is strongly protected
|
= 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

View File

@ -1,8 +1,8 @@
error: Undefined Behavior: not granting access to tag <TAG> because that would remove [SharedReadOnly for <TAG>] which is strongly protected because it is an argument of call ID
error: Undefined Behavior: not granting access to tag <TAG> because that would remove [SharedReadOnly for <TAG>] which is strongly protected
--> $DIR/aliasing_mut4.rs:LL:CC
|
LL | pub fn safe(x: &i32, y: &mut Cell<i32>) {
| ^ not granting access to tag <TAG> because that would remove [SharedReadOnly for <TAG>] which is strongly protected because it is an argument of call ID
| ^ not granting access to tag <TAG> because that would remove [SharedReadOnly for <TAG>] which is strongly protected
|
= 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

View File

@ -1,8 +1,8 @@
error: Undefined Behavior: not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is weakly protected because it is an argument of call ID
error: Undefined Behavior: not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is weakly protected
--> $DIR/box_noalias_violation.rs:LL:CC
|
LL | *y
| ^^ not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is weakly protected because it is an argument of call ID
| ^^ not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is weakly protected
|
= 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

View File

@ -1,8 +1,8 @@
error: Undefined Behavior: not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected because it is an argument of call ID
error: Undefined Behavior: not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected
--> $DIR/illegal_write6.rs:LL:CC
|
LL | unsafe { *y = 2 };
| ^^^^^^ not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected because it is an argument of call ID
| ^^^^^^ not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected
|
= 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

View File

@ -1,8 +1,8 @@
error: Undefined Behavior: not granting access to tag <TAG> because that would remove [SharedReadOnly for <TAG>] which is strongly protected because it is an argument of call ID
error: Undefined Behavior: not granting access to tag <TAG> because that would remove [SharedReadOnly for <TAG>] which is strongly protected
--> $DIR/invalidate_against_protector2.rs:LL:CC
|
LL | unsafe { *x = 0 };
| ^^^^^^ not granting access to tag <TAG> because that would remove [SharedReadOnly for <TAG>] which is strongly protected because it is an argument of call ID
| ^^^^^^ not granting access to tag <TAG> because that would remove [SharedReadOnly for <TAG>] which is strongly protected
|
= 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

View File

@ -1,8 +1,8 @@
error: Undefined Behavior: not granting access to tag <TAG> because that would remove [SharedReadOnly for <TAG>] which is strongly protected because it is an argument of call ID
error: Undefined Behavior: not granting access to tag <TAG> because that would remove [SharedReadOnly for <TAG>] which is strongly protected
--> $DIR/invalidate_against_protector3.rs:LL:CC
|
LL | unsafe { *x = 0 };
| ^^^^^^ not granting access to tag <TAG> because that would remove [SharedReadOnly for <TAG>] which is strongly protected because it is an argument of call ID
| ^^^^^^ not granting access to tag <TAG> because that would remove [SharedReadOnly for <TAG>] which is strongly protected
|
= 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

View File

@ -1,8 +1,8 @@
error: Undefined Behavior: not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected because it is an argument of call ID
error: Undefined Behavior: not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected
--> RUSTLIB/alloc/src/boxed.rs:LL:CC
|
LL | Box(unsafe { Unique::new_unchecked(raw) }, alloc)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected because it is an argument of call ID
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected
|
= 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

View File

@ -1,8 +1,8 @@
error: Undefined Behavior: not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected because it is an argument of call ID
error: Undefined Behavior: not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected
--> RUSTLIB/alloc/src/boxed.rs:LL:CC
|
LL | Box(unsafe { Unique::new_unchecked(raw) }, alloc)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected because it is an argument of call ID
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected
|
= 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

View File

@ -1,8 +1,8 @@
error: Undefined Behavior: not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected because it is an argument of call ID
error: Undefined Behavior: not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected
--> $DIR/arg_inplace_mutate.rs:LL:CC
|
LL | unsafe { ptr.write(S(0)) };
| ^^^^^^^^^^^^^^^ not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected because it is an argument of call ID
| ^^^^^^^^^^^^^^^ not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected
|
= 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

View File

@ -1,8 +1,8 @@
error: Undefined Behavior: not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected because it is an argument of call ID
error: Undefined Behavior: not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected
--> $DIR/arg_inplace_observe_during.rs:LL:CC
|
LL | unsafe { ptr.read() };
| ^^^^^^^^^^ not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected because it is an argument of call ID
| ^^^^^^^^^^ not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected
|
= 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

View File

@ -1,5 +1,5 @@
error: Undefined Behavior: using uninitialized data, but this operation requires initialized memory
--> $DIR/return_pointer_aliasing.rs:LL:CC
--> $DIR/return_pointer_aliasing_read.rs:LL:CC
|
LL | unsafe { ptr.read() };
| ^^^^^^^^^^ using uninitialized data, but this operation requires initialized memory
@ -7,9 +7,9 @@ LL | unsafe { ptr.read() };
= 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 `myfun` at $DIR/return_pointer_aliasing.rs:LL:CC
= note: inside `myfun` at $DIR/return_pointer_aliasing_read.rs:LL:CC
note: inside `main`
--> $DIR/return_pointer_aliasing.rs:LL:CC
--> $DIR/return_pointer_aliasing_read.rs:LL:CC
|
LL | Call(*ptr = myfun(ptr), ReturnTo(after_call), UnwindContinue())
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -1,13 +1,13 @@
error: Undefined Behavior: not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected because it is an argument of call ID
--> $DIR/return_pointer_aliasing.rs:LL:CC
error: Undefined Behavior: not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected
--> $DIR/return_pointer_aliasing_read.rs:LL:CC
|
LL | unsafe { ptr.read() };
| ^^^^^^^^^^ not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected because it is an argument of call ID
| ^^^^^^^^^^ not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected
|
= 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: <TAG> was created by a SharedReadWrite retag at offsets [0x0..0x4]
--> $DIR/return_pointer_aliasing.rs:LL:CC
--> $DIR/return_pointer_aliasing_read.rs:LL:CC
|
LL | / mir! {
LL | | {
@ -18,14 +18,14 @@ LL | | }
LL | | }
| |_____^
help: <TAG> is this argument
--> $DIR/return_pointer_aliasing.rs:LL:CC
--> $DIR/return_pointer_aliasing_read.rs:LL:CC
|
LL | unsafe { ptr.read() };
| ^^^^^^^^^^^^^^^^^^^^^
= note: BACKTRACE (of the first span):
= note: inside `myfun` at $DIR/return_pointer_aliasing.rs:LL:CC
= note: inside `myfun` at $DIR/return_pointer_aliasing_read.rs:LL:CC
note: inside `main`
--> $DIR/return_pointer_aliasing.rs:LL:CC
--> $DIR/return_pointer_aliasing_read.rs:LL:CC
|
LL | Call(*ptr = myfun(ptr), ReturnTo(after_call), UnwindContinue())
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -1,5 +1,5 @@
error: Undefined Behavior: read access through <TAG> (root of the allocation) at ALLOC[0x0] is forbidden
--> $DIR/return_pointer_aliasing.rs:LL:CC
--> $DIR/return_pointer_aliasing_read.rs:LL:CC
|
LL | unsafe { ptr.read() };
| ^^^^^^^^^^ read access through <TAG> (root of the allocation) at ALLOC[0x0] is forbidden
@ -9,7 +9,7 @@ LL | unsafe { ptr.read() };
= help: this foreign read access would cause the protected tag <TAG> (currently Active) to become Disabled
= help: protected tags must never be Disabled
help: the accessed tag <TAG> was created here
--> $DIR/return_pointer_aliasing.rs:LL:CC
--> $DIR/return_pointer_aliasing_read.rs:LL:CC
|
LL | / mir! {
LL | | {
@ -20,20 +20,20 @@ LL | | }
LL | | }
| |_____^
help: the protected tag <TAG> was created here, in the initial state Reserved
--> $DIR/return_pointer_aliasing.rs:LL:CC
--> $DIR/return_pointer_aliasing_read.rs:LL:CC
|
LL | unsafe { ptr.read() };
| ^^^^^^^^^^^^^^^^^^^^^
help: the protected tag <TAG> later transitioned to Active due to a child write access at offsets [0x0..0x4]
--> $DIR/return_pointer_aliasing.rs:LL:CC
--> $DIR/return_pointer_aliasing_read.rs:LL:CC
|
LL | unsafe { ptr.read() };
| ^^^^^^^^^^^^^^^^^^^^^
= help: this transition corresponds to the first write to a 2-phase borrowed mutable reference
= note: BACKTRACE (of the first span):
= note: inside `myfun` at $DIR/return_pointer_aliasing.rs:LL:CC
= note: inside `myfun` at $DIR/return_pointer_aliasing_read.rs:LL:CC
note: inside `main`
--> $DIR/return_pointer_aliasing.rs:LL:CC
--> $DIR/return_pointer_aliasing_read.rs:LL:CC
|
LL | Call(*ptr = myfun(ptr), ReturnTo(after_call), UnwindContinue())
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -1,4 +1,4 @@
// This does need an aliasing model.
// This does need an aliasing model and protectors.
//@revisions: stack tree
//@[tree]compile-flags: -Zmiri-tree-borrows
#![feature(raw_ref_op)]
@ -14,8 +14,8 @@ pub fn main() {
let _x = 0;
let ptr = &raw mut _x;
// We arrange for `myfun` to have a pointer that aliases
// its return place. Even just reading from that pointer is UB.
Call(_x = myfun(ptr), ReturnTo(after_call), UnwindContinue())
// its return place. Writing to that pointer is UB.
Call(*ptr = myfun(ptr), ReturnTo(after_call), UnwindContinue())
}
after_call = {
@ -27,7 +27,7 @@ pub fn main() {
fn myfun(ptr: *mut i32) -> i32 {
// This overwrites the return place, which shouldn't be possible through another pointer.
unsafe { ptr.write(0) };
//~[stack]^ ERROR: tag does not exist in the borrow stack
//~[stack]^ ERROR: strongly protected
//~[tree]| ERROR: /write access .* forbidden/
13
}

View File

@ -1,16 +1,13 @@
error: Undefined Behavior: attempting a write access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
--> $DIR/return_pointer_aliasing2.rs:LL:CC
error: Undefined Behavior: not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected
--> $DIR/return_pointer_aliasing_write.rs:LL:CC
|
LL | unsafe { ptr.write(0) };
| ^^^^^^^^^^^^
| |
| attempting a write access using <TAG> 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]
| ^^^^^^^^^^^^ not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected
|
= 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: <TAG> was created by a SharedReadWrite retag at offsets [0x0..0x4]
--> $DIR/return_pointer_aliasing2.rs:LL:CC
--> $DIR/return_pointer_aliasing_write.rs:LL:CC
|
LL | / mir! {
LL | | {
@ -20,18 +17,18 @@ LL | | let ptr = &raw mut _x;
LL | | }
LL | | }
| |_____^
help: <TAG> was later invalidated at offsets [0x0..0x4] by a Unique in-place function argument/return passing protection
--> $DIR/return_pointer_aliasing2.rs:LL:CC
help: <TAG> is this argument
--> $DIR/return_pointer_aliasing_write.rs:LL:CC
|
LL | unsafe { ptr.write(0) };
| ^^^^^^^^^^^^^^^^^^^^^^^
= note: BACKTRACE (of the first span):
= note: inside `myfun` at $DIR/return_pointer_aliasing2.rs:LL:CC
= note: inside `myfun` at $DIR/return_pointer_aliasing_write.rs:LL:CC
note: inside `main`
--> $DIR/return_pointer_aliasing2.rs:LL:CC
--> $DIR/return_pointer_aliasing_write.rs:LL:CC
|
LL | Call(_x = myfun(ptr), ReturnTo(after_call), UnwindContinue())
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
LL | Call(*ptr = myfun(ptr), ReturnTo(after_call), UnwindContinue())
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: this error originates in the macro `::core::intrinsics::mir::__internal_remove_let` which comes from the expansion of the macro `mir` (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

View File

@ -1,5 +1,5 @@
error: Undefined Behavior: write access through <TAG> (root of the allocation) at ALLOC[0x0] is forbidden
--> $DIR/return_pointer_aliasing2.rs:LL:CC
--> $DIR/return_pointer_aliasing_write.rs:LL:CC
|
LL | unsafe { ptr.write(0) };
| ^^^^^^^^^^^^ write access through <TAG> (root of the allocation) at ALLOC[0x0] is forbidden
@ -9,7 +9,7 @@ LL | unsafe { ptr.write(0) };
= help: this foreign write access would cause the protected tag <TAG> (currently Active) to become Disabled
= help: protected tags must never be Disabled
help: the accessed tag <TAG> was created here
--> $DIR/return_pointer_aliasing2.rs:LL:CC
--> $DIR/return_pointer_aliasing_write.rs:LL:CC
|
LL | / mir! {
LL | | {
@ -20,23 +20,23 @@ LL | | }
LL | | }
| |_____^
help: the protected tag <TAG> was created here, in the initial state Reserved
--> $DIR/return_pointer_aliasing2.rs:LL:CC
--> $DIR/return_pointer_aliasing_write.rs:LL:CC
|
LL | unsafe { ptr.write(0) };
| ^^^^^^^^^^^^^^^^^^^^^^^
help: the protected tag <TAG> later transitioned to Active due to a child write access at offsets [0x0..0x4]
--> $DIR/return_pointer_aliasing2.rs:LL:CC
--> $DIR/return_pointer_aliasing_write.rs:LL:CC
|
LL | unsafe { ptr.write(0) };
| ^^^^^^^^^^^^^^^^^^^^^^^
= help: this transition corresponds to the first write to a 2-phase borrowed mutable reference
= note: BACKTRACE (of the first span):
= note: inside `myfun` at $DIR/return_pointer_aliasing2.rs:LL:CC
= note: inside `myfun` at $DIR/return_pointer_aliasing_write.rs:LL:CC
note: inside `main`
--> $DIR/return_pointer_aliasing2.rs:LL:CC
--> $DIR/return_pointer_aliasing_write.rs:LL:CC
|
LL | Call(_x = myfun(ptr), ReturnTo(after_call), UnwindContinue())
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
LL | Call(*ptr = myfun(ptr), ReturnTo(after_call), UnwindContinue())
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: this error originates in the macro `::core::intrinsics::mir::__internal_remove_let` which comes from the expansion of the macro `mir` (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

View File

@ -0,0 +1,39 @@
// This does need an aliasing model and protectors.
//@revisions: stack tree
//@[tree]compile-flags: -Zmiri-tree-borrows
#![feature(raw_ref_op)]
#![feature(core_intrinsics)]
#![feature(custom_mir)]
#![feature(explicit_tail_calls)]
#![allow(incomplete_features)]
use std::intrinsics::mir::*;
#[custom_mir(dialect = "runtime", phase = "optimized")]
pub fn main() {
mir! {
{
let _x = 0;
let ptr = &raw mut _x;
// We arrange for `myfun` to have a pointer that aliases
// its return place. Writing to that pointer is UB.
Call(*ptr = myfun(ptr), ReturnTo(after_call), UnwindContinue())
}
after_call = {
Return()
}
}
}
fn myfun(ptr: *mut i32) -> i32 {
become myfun2(ptr)
}
fn myfun2(ptr: *mut i32) -> i32 {
// This overwrites the return place, which shouldn't be possible through another pointer.
unsafe { ptr.write(0) };
//~[stack]^ ERROR: strongly protected
//~[tree]| ERROR: /write access .* forbidden/
13
}

View File

@ -0,0 +1,37 @@
error: Undefined Behavior: not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected
--> $DIR/return_pointer_aliasing_write_tail_call.rs:LL:CC
|
LL | unsafe { ptr.write(0) };
| ^^^^^^^^^^^^ not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected
|
= 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: <TAG> was created by a SharedReadWrite retag at offsets [0x0..0x4]
--> $DIR/return_pointer_aliasing_write_tail_call.rs:LL:CC
|
LL | / mir! {
LL | | {
LL | | let _x = 0;
LL | | let ptr = &raw mut _x;
... |
LL | | }
LL | | }
| |_____^
help: <TAG> is this argument
--> $DIR/return_pointer_aliasing_write_tail_call.rs:LL:CC
|
LL | unsafe { ptr.write(0) };
| ^^^^^^^^^^^^^^^^^^^^^^^
= note: BACKTRACE (of the first span):
= note: inside `myfun2` at $DIR/return_pointer_aliasing_write_tail_call.rs:LL:CC
note: inside `main`
--> $DIR/return_pointer_aliasing_write_tail_call.rs:LL:CC
|
LL | Call(*ptr = myfun(ptr), ReturnTo(after_call), UnwindContinue())
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: this error originates in the macro `::core::intrinsics::mir::__internal_remove_let` which comes from the expansion of the macro `mir` (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 1 previous error

View File

@ -0,0 +1,45 @@
error: Undefined Behavior: write access through <TAG> (root of the allocation) at ALLOC[0x0] is forbidden
--> $DIR/return_pointer_aliasing_write_tail_call.rs:LL:CC
|
LL | unsafe { ptr.write(0) };
| ^^^^^^^^^^^^ write access through <TAG> (root of the allocation) at ALLOC[0x0] is forbidden
|
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
= help: the accessed tag <TAG> (root of the allocation) is foreign to the protected tag <TAG> (i.e., it is not a child)
= help: this foreign write access would cause the protected tag <TAG> (currently Active) to become Disabled
= help: protected tags must never be Disabled
help: the accessed tag <TAG> was created here
--> $DIR/return_pointer_aliasing_write_tail_call.rs:LL:CC
|
LL | / mir! {
LL | | {
LL | | let _x = 0;
LL | | let ptr = &raw mut _x;
... |
LL | | }
LL | | }
| |_____^
help: the protected tag <TAG> was created here, in the initial state Reserved
--> $DIR/return_pointer_aliasing_write_tail_call.rs:LL:CC
|
LL | unsafe { ptr.write(0) };
| ^^^^^^^^^^^^^^^^^^^^^^^
help: the protected tag <TAG> later transitioned to Active due to a child write access at offsets [0x0..0x4]
--> $DIR/return_pointer_aliasing_write_tail_call.rs:LL:CC
|
LL | unsafe { ptr.write(0) };
| ^^^^^^^^^^^^^^^^^^^^^^^
= help: this transition corresponds to the first write to a 2-phase borrowed mutable reference
= note: BACKTRACE (of the first span):
= note: inside `myfun2` at $DIR/return_pointer_aliasing_write_tail_call.rs:LL:CC
note: inside `main`
--> $DIR/return_pointer_aliasing_write_tail_call.rs:LL:CC
|
LL | Call(*ptr = myfun(ptr), ReturnTo(after_call), UnwindContinue())
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: this error originates in the macro `::core::intrinsics::mir::__internal_remove_let` which comes from the expansion of the macro `mir` (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 1 previous error

View File

@ -24,7 +24,7 @@ fn main() {
unsafe {
// Pass, since SSE is enabled
addss(_mm_setzero_ps(), _mm_setzero_ps());
minss(_mm_setzero_ps(), _mm_setzero_ps());
// Fail, since SSE4.1 is not enabled
dpps(_mm_setzero_ps(), _mm_setzero_ps(), 0);
@ -34,8 +34,8 @@ fn main() {
#[allow(improper_ctypes)]
extern "C" {
#[link_name = "llvm.x86.sse.add.ss"]
fn addss(a: __m128, b: __m128) -> __m128;
#[link_name = "llvm.x86.sse.min.ss"]
fn minss(a: __m128, b: __m128) -> __m128;
#[link_name = "llvm.x86.sse41.dpps"]
fn dpps(a: __m128, b: __m128, imm8: u8) -> __m128;

View File

@ -1,8 +1,8 @@
error: Undefined Behavior: deallocating while item [Unique for <TAG>] is strongly protected by call ID
error: Undefined Behavior: deallocating while item [Unique for <TAG>] is strongly protected
--> RUSTLIB/alloc/src/alloc.rs:LL:CC
|
LL | unsafe { __rust_dealloc(ptr, layout.size(), layout.align()) }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ deallocating while item [Unique for <TAG>] is strongly protected by call ID
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ deallocating while item [Unique for <TAG>] is strongly protected
|
= 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

View File

@ -1,8 +1,8 @@
error: Undefined Behavior: not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected because it is an argument of call ID
error: Undefined Behavior: not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected
--> $DIR/drop_in_place_protector.rs:LL:CC
|
LL | let _val = *P;
| ^^ not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected because it is an argument of call ID
| ^^ not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected
|
= 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

View File

@ -1,8 +1,8 @@
error: Undefined Behavior: not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected because it is an argument of call ID
error: Undefined Behavior: not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected
--> $DIR/invalidate_against_protector1.rs:LL:CC
|
LL | let _val = unsafe { *x };
| ^^ not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected because it is an argument of call ID
| ^^ not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is strongly protected
|
= 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

View File

@ -2,11 +2,11 @@
Warning: this tree is indicative only. Some tags may have been hidden.
0.. 1
| Act | └─┬──<TAG=root of the allocation>
| RsM | └─┬──<TAG=base>
| RsM | ├─┬──<TAG=x>
| RsM | │ └─┬──<TAG=caller:x>
| Rs | │ └────<TAG=callee:x> Strongly protected
| RsM | └────<TAG=y, callee:y, caller:y>
| ReIM| └─┬──<TAG=base>
| ReIM| ├─┬──<TAG=x>
| ReIM| │ └─┬──<TAG=caller:x>
| Res | │ └────<TAG=callee:x> Strongly protected
| ReIM| └────<TAG=y, callee:y, caller:y>
──────────────────────────────────────────────────
error: Undefined Behavior: write access through <TAG> (y, callee:y, caller:y) at ALLOC[0x0] is forbidden
--> $DIR/cell-protected-write.rs:LL:CC

View File

@ -2,11 +2,11 @@
Warning: this tree is indicative only. Some tags may have been hidden.
0.. 1
| Act | └─┬──<TAG=root of the allocation>
| Rs | └─┬──<TAG=n>
| Rs | ├─┬──<TAG=x>
| Rs | │ └─┬──<TAG=caller:x>
| Rs | │ └────<TAG=callee:x> Strongly protected
| Rs | └────<TAG=y, callee:y, caller:y>
| Res | └─┬──<TAG=n>
| Res | ├─┬──<TAG=x>
| Res | │ └─┬──<TAG=caller:x>
| Res | │ └────<TAG=callee:x> Strongly protected
| Res | └────<TAG=y, callee:y, caller:y>
──────────────────────────────────────────────────
error: Undefined Behavior: write access through <TAG> (y, callee:y, caller:y) at ALLOC[0x0] is forbidden
--> $DIR/int-protected-write.rs:LL:CC

View File

@ -0,0 +1,652 @@
//@only-target-linux
#![feature(exposed_provenance)] // Needed for fn test_pointer()
use std::convert::TryInto;
use std::mem::MaybeUninit;
fn main() {
test_epoll_socketpair();
test_epoll_socketpair_both_sides();
test_socketpair_read();
test_epoll_eventfd();
test_event_overwrite();
test_not_fully_closed_fd();
test_closed_fd();
test_two_epoll_instance();
test_no_notification_for_unregister_flag();
test_epoll_ctl_mod();
test_epoll_ctl_del();
test_pointer();
test_two_same_fd_in_same_epoll_instance();
test_epoll_wait_maxevent_zero();
test_socketpair_epollerr();
test_epoll_lost_events();
test_ready_list_fetching_logic();
}
// Using `as` cast since `EPOLLET` wraps around
const EPOLL_IN_OUT_ET: u32 = (libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET) as _;
#[track_caller]
fn check_epoll_wait<const N: usize>(epfd: i32, expected_notifications: &[(u32, u64)]) {
let epoll_event = libc::epoll_event { events: 0, u64: 0 };
let mut array: [libc::epoll_event; N] = [epoll_event; N];
let maxsize = N;
let array_ptr = array.as_mut_ptr();
let res = unsafe { libc::epoll_wait(epfd, array_ptr, maxsize.try_into().unwrap(), 0) };
if res < 0 {
panic!("epoll_wait failed: {}", std::io::Error::last_os_error());
}
assert_eq!(
res,
expected_notifications.len().try_into().unwrap(),
"got wrong number of notifications"
);
let slice = unsafe { std::slice::from_raw_parts(array_ptr, res.try_into().unwrap()) };
for (return_event, expected_event) in slice.iter().zip(expected_notifications.iter()) {
let event = return_event.events;
let data = return_event.u64;
assert_eq!(event, expected_event.0, "got wrong events");
assert_eq!(data, expected_event.1, "got wrong data");
}
}
fn test_epoll_socketpair() {
// Create an epoll instance.
let epfd = unsafe { libc::epoll_create1(0) };
assert_ne!(epfd, -1);
// Create a socketpair instance.
let mut fds = [-1, -1];
let res = unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
assert_eq!(res, 0);
// Write to fd[0]
let data = "abcde".as_bytes().as_ptr();
let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 5) };
assert_eq!(res, 5);
// Register fd[1] with EPOLLIN|EPOLLOUT|EPOLLET|EPOLLRDHUP
let mut ev = libc::epoll_event {
events: (libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET | libc::EPOLLRDHUP) as _,
u64: u64::try_from(fds[1]).unwrap(),
};
let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fds[1], &mut ev) };
assert_eq!(res, 0);
// Check result from epoll_wait.
let expected_event = u32::try_from(libc::EPOLLIN | libc::EPOLLOUT).unwrap();
let expected_value = u64::try_from(fds[1]).unwrap();
check_epoll_wait::<8>(epfd, &[(expected_event, expected_value)]);
// Check that this is indeed using "ET" (edge-trigger) semantics: a second epoll should return nothing.
check_epoll_wait::<8>(epfd, &[]);
// Write some more to fd[0].
let data = "abcde".as_bytes().as_ptr();
let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 5) };
assert_eq!(res, 5);
// This did not change the readiness of fd[1]. And yet, we're seeing the event reported
// again by the kernel, so Miri does the same.
check_epoll_wait::<8>(epfd, &[(expected_event, expected_value)]);
// Close the peer socketpair.
let res = unsafe { libc::close(fds[0]) };
assert_eq!(res, 0);
// Check result from epoll_wait.
// We expect to get a read, write, HUP notification from the close since closing an FD always unblocks reads and writes on its peer.
let expected_event =
u32::try_from(libc::EPOLLRDHUP | libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLHUP).unwrap();
let expected_value = u64::try_from(fds[1]).unwrap();
check_epoll_wait::<8>(epfd, &[(expected_event, expected_value)]);
}
// This test first registers a file description with a flag that does not lead to notification,
// then EPOLL_CTL_MOD to add another flag that will lead to notification.
fn test_epoll_ctl_mod() {
// Create an epoll instance.
let epfd = unsafe { libc::epoll_create1(0) };
assert_ne!(epfd, -1);
// Create a socketpair instance.
let mut fds = [-1, -1];
let res = unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
assert_eq!(res, 0);
// Register fd[1] with EPOLLIN|EPOLLET.
let mut ev = libc::epoll_event {
events: (libc::EPOLLIN | libc::EPOLLET) as _,
u64: u64::try_from(fds[1]).unwrap(),
};
let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fds[1], &mut ev) };
assert_eq!(res, 0);
// Check result from epoll_wait. No notification would be returned.
check_epoll_wait::<8>(epfd, &[]);
// Use EPOLL_CTL_MOD to change to EPOLLOUT flag.
let mut ev = libc::epoll_event {
events: (libc::EPOLLOUT | libc::EPOLLET) as _,
u64: u64::try_from(fds[1]).unwrap(),
};
let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_MOD, fds[1], &mut ev) };
assert_eq!(res, 0);
// Check result from epoll_wait. EPOLLOUT notification is expected.
let expected_event = u32::try_from(libc::EPOLLOUT).unwrap();
let expected_value = u64::try_from(fds[1]).unwrap();
check_epoll_wait::<8>(epfd, &[(expected_event, expected_value)]);
}
fn test_epoll_ctl_del() {
// Create an epoll instance.
let epfd = unsafe { libc::epoll_create1(0) };
assert_ne!(epfd, -1);
// Create a socketpair instance.
let mut fds = [-1, -1];
let res = unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
assert_eq!(res, 0);
// Write to fd[0]
let data = "abcde".as_bytes().as_ptr();
let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 5) };
assert_eq!(res, 5);
// Register fd[1] with EPOLLIN|EPOLLOUT|EPOLLET
let mut ev = libc::epoll_event { events: EPOLL_IN_OUT_ET, u64: u64::try_from(fds[1]).unwrap() };
let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fds[1], &mut ev) };
assert_eq!(res, 0);
// Test EPOLL_CTL_DEL.
let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_DEL, fds[1], &mut ev) };
assert_eq!(res, 0);
check_epoll_wait::<8>(epfd, &[]);
}
// This test is for one fd registered under two different epoll instance.
fn test_two_epoll_instance() {
// Create two epoll instance.
let epfd1 = unsafe { libc::epoll_create1(0) };
assert_ne!(epfd1, -1);
let epfd2 = unsafe { libc::epoll_create1(0) };
assert_ne!(epfd2, -1);
// Create a socketpair instance.
let mut fds = [-1, -1];
let res = unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
assert_eq!(res, 0);
// Write to the socketpair.
let data = "abcde".as_bytes().as_ptr();
let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 5) };
assert_eq!(res, 5);
// Register one side of the socketpair with EPOLLIN | EPOLLOUT | EPOLLET.
let mut ev = libc::epoll_event { events: EPOLL_IN_OUT_ET, u64: u64::try_from(fds[1]).unwrap() };
let res = unsafe { libc::epoll_ctl(epfd1, libc::EPOLL_CTL_ADD, fds[1], &mut ev) };
assert_eq!(res, 0);
let res = unsafe { libc::epoll_ctl(epfd2, libc::EPOLL_CTL_ADD, fds[1], &mut ev) };
assert_eq!(res, 0);
// Notification should be received from both instance of epoll.
let expected_event = u32::try_from(libc::EPOLLIN | libc::EPOLLOUT).unwrap();
let expected_value = u64::try_from(fds[1]).unwrap();
check_epoll_wait::<8>(epfd1, &[(expected_event, expected_value)]);
check_epoll_wait::<8>(epfd2, &[(expected_event, expected_value)]);
}
// This test is for two same file description registered under the same epoll instance through dup.
// Notification should be provided for both.
fn test_two_same_fd_in_same_epoll_instance() {
// Create an epoll instance.
let epfd = unsafe { libc::epoll_create1(0) };
assert_ne!(epfd, -1);
// Create a socketpair instance.
let mut fds = [-1, -1];
let res = unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
assert_eq!(res, 0);
// Dup the fd.
let newfd = unsafe { libc::dup(fds[1]) };
assert_ne!(newfd, -1);
// Register both fd to the same epoll instance.
let mut ev = libc::epoll_event { events: EPOLL_IN_OUT_ET, u64: 5 as u64 };
let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fds[1], &mut ev) };
assert_eq!(res, 0);
let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, newfd, &mut ev) };
assert_eq!(res, 0);
// Write to the socketpair.
let data = "abcde".as_bytes().as_ptr();
let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 5) };
assert_eq!(res, 5);
//Two notification should be received.
let expected_event = u32::try_from(libc::EPOLLIN | libc::EPOLLOUT).unwrap();
let expected_value = 5 as u64;
check_epoll_wait::<8>(
epfd,
&[(expected_event, expected_value), (expected_event, expected_value)],
);
}
fn test_epoll_eventfd() {
// Create an eventfd instance.
let flags = libc::EFD_NONBLOCK | libc::EFD_CLOEXEC;
let fd = unsafe { libc::eventfd(0, flags) };
// Write to the eventfd instance.
let sized_8_data: [u8; 8] = 1_u64.to_ne_bytes();
let res = unsafe { libc::write(fd, sized_8_data.as_ptr() as *const libc::c_void, 8) };
assert_eq!(res, 8);
// Create an epoll instance.
let epfd = unsafe { libc::epoll_create1(0) };
assert_ne!(epfd, -1);
// Register eventfd with EPOLLIN | EPOLLOUT | EPOLLET
let mut ev = libc::epoll_event { events: EPOLL_IN_OUT_ET, u64: u64::try_from(fd).unwrap() };
let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fd, &mut ev) };
assert_eq!(res, 0);
// Check result from epoll_wait.
let expected_event = u32::try_from(libc::EPOLLIN | libc::EPOLLOUT).unwrap();
let expected_value = u64::try_from(fd).unwrap();
check_epoll_wait::<8>(epfd, &[(expected_event, expected_value)]);
}
fn test_pointer() {
// Create an epoll instance.
let epfd = unsafe { libc::epoll_create1(0) };
assert_ne!(epfd, -1);
// Create a socketpair instance.
let mut fds = [-1, -1];
let res = unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
assert_eq!(res, 0);
// Register fd[1] with EPOLLIN|EPOLLOUT|EPOLLET
let data = MaybeUninit::<u64>::uninit().as_ptr();
let mut ev =
libc::epoll_event { events: EPOLL_IN_OUT_ET, u64: data.expose_provenance() as u64 };
let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fds[1], &mut ev) };
assert_eq!(res, 0);
}
// When read/write happened on one side of the socketpair, only the other side will be notified.
fn test_epoll_socketpair_both_sides() {
// Create an epoll instance.
let epfd = unsafe { libc::epoll_create1(0) };
assert_ne!(epfd, -1);
// Create a socketpair instance.
let mut fds = [-1, -1];
let res = unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
assert_eq!(res, 0);
// Register both fd to the same epoll instance.
let mut ev = libc::epoll_event { events: EPOLL_IN_OUT_ET, u64: fds[0] as u64 };
let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fds[0], &mut ev) };
assert_eq!(res, 0);
let mut ev = libc::epoll_event { events: EPOLL_IN_OUT_ET, u64: fds[1] as u64 };
let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fds[1], &mut ev) };
assert_eq!(res, 0);
// Write to fds[1].
let data = "abcde".as_bytes().as_ptr();
let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 5) };
assert_eq!(res, 5);
//Two notification should be received.
let expected_event0 = u32::try_from(libc::EPOLLIN | libc::EPOLLOUT).unwrap();
let expected_value0 = fds[0] as u64;
let expected_event1 = u32::try_from(libc::EPOLLOUT).unwrap();
let expected_value1 = fds[1] as u64;
check_epoll_wait::<8>(
epfd,
&[(expected_event0, expected_value0), (expected_event1, expected_value1)],
);
// Read from fds[0].
let mut buf: [u8; 5] = [0; 5];
let res = unsafe { libc::read(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
assert_eq!(res, 5);
assert_eq!(buf, "abcde".as_bytes());
// Notification should be provided for fds[1].
let expected_event = u32::try_from(libc::EPOLLOUT).unwrap();
let expected_value = fds[1] as u64;
check_epoll_wait::<8>(epfd, &[(expected_event, expected_value)]);
}
// When file description is fully closed, epoll_wait should not provide any notification for
// that file description.
fn test_closed_fd() {
// Create an epoll instance.
let epfd = unsafe { libc::epoll_create1(0) };
assert_ne!(epfd, -1);
// Create an eventfd instance.
let flags = libc::EFD_NONBLOCK | libc::EFD_CLOEXEC;
let fd = unsafe { libc::eventfd(0, flags) };
// Register eventfd with EPOLLIN | EPOLLOUT | EPOLLET
let mut ev = libc::epoll_event { events: EPOLL_IN_OUT_ET, u64: u64::try_from(fd).unwrap() };
let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fd, &mut ev) };
assert_eq!(res, 0);
// Write to the eventfd instance.
let sized_8_data: [u8; 8] = 1_u64.to_ne_bytes();
let res = unsafe { libc::write(fd, sized_8_data.as_ptr() as *const libc::c_void, 8) };
assert_eq!(res, 8);
// Close the eventfd.
let res = unsafe { libc::close(fd) };
assert_eq!(res, 0);
// No notification should be provided because the file description is closed.
check_epoll_wait::<8>(epfd, &[]);
}
// When a certain file descriptor registered with epoll is closed, but the underlying file description
// is not closed, notification should still be provided.
//
// This is a quirk of epoll being described in https://man7.org/linux/man-pages/man7/epoll.7.html
// A file descriptor is removed from an interest list only after all the file descriptors
// referring to the underlying open file description have been closed.
fn test_not_fully_closed_fd() {
// Create an epoll instance.
let epfd = unsafe { libc::epoll_create1(0) };
assert_ne!(epfd, -1);
// Create an eventfd instance.
let flags = libc::EFD_NONBLOCK | libc::EFD_CLOEXEC;
let fd = unsafe { libc::eventfd(0, flags) };
// Dup the fd.
let newfd = unsafe { libc::dup(fd) };
assert_ne!(newfd, -1);
// Register eventfd with EPOLLIN | EPOLLOUT | EPOLLET
let mut ev = libc::epoll_event { events: EPOLL_IN_OUT_ET, u64: u64::try_from(fd).unwrap() };
let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fd, &mut ev) };
assert_eq!(res, 0);
// Close the original fd that being used to register with epoll.
let res = unsafe { libc::close(fd) };
assert_eq!(res, 0);
// Notification should still be provided because the file description is not closed.
let expected_event = u32::try_from(libc::EPOLLOUT).unwrap();
let expected_value = fd as u64;
check_epoll_wait::<1>(epfd, &[(expected_event, expected_value)]);
// Write to the eventfd instance to produce notification.
let sized_8_data: [u8; 8] = 1_u64.to_ne_bytes();
let res = unsafe { libc::write(newfd, sized_8_data.as_ptr() as *const libc::c_void, 8) };
assert_eq!(res, 8);
// Close the dupped fd.
let res = unsafe { libc::close(newfd) };
assert_eq!(res, 0);
// No notification should be provided.
check_epoll_wait::<1>(epfd, &[]);
}
// Each time a notification is provided, it should reflect the file description's readiness
// at the moment the latest event occurred.
fn test_event_overwrite() {
// Create an eventfd instance.
let flags = libc::EFD_NONBLOCK | libc::EFD_CLOEXEC;
let fd = unsafe { libc::eventfd(0, flags) };
// Write to the eventfd instance.
let sized_8_data: [u8; 8] = 1_u64.to_ne_bytes();
let res = unsafe { libc::write(fd, sized_8_data.as_ptr() as *const libc::c_void, 8) };
assert_eq!(res, 8);
// Create an epoll instance.
let epfd = unsafe { libc::epoll_create1(0) };
assert_ne!(epfd, -1);
// Register eventfd with EPOLLIN | EPOLLOUT | EPOLLET
let mut ev = libc::epoll_event {
events: (libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET) as _,
u64: u64::try_from(fd).unwrap(),
};
let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fd, &mut ev) };
assert_eq!(res, 0);
// Read from the eventfd instance.
let mut buf: [u8; 8] = [0; 8];
let res = unsafe { libc::read(fd, buf.as_mut_ptr().cast(), 8) };
assert_eq!(res, 8);
// Check result from epoll_wait.
let expected_event = u32::try_from(libc::EPOLLOUT).unwrap();
let expected_value = u64::try_from(fd).unwrap();
check_epoll_wait::<8>(epfd, &[(expected_event, expected_value)]);
}
// An epoll notification will be provided for every succesful read in a socketpair.
// This behaviour differs from the real system.
fn test_socketpair_read() {
// Create an epoll instance.
let epfd = unsafe { libc::epoll_create1(0) };
assert_ne!(epfd, -1);
// Create a socketpair instance.
let mut fds = [-1, -1];
let res = unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
assert_eq!(res, 0);
// Register both fd to the same epoll instance.
let mut ev = libc::epoll_event {
events: (libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET) as _,
u64: fds[0] as u64,
};
let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fds[0], &mut ev) };
assert_eq!(res, 0);
let mut ev = libc::epoll_event {
events: (libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET) as _,
u64: fds[1] as u64,
};
let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fds[1], &mut ev) };
assert_eq!(res, 0);
// Write 5 bytes to fds[1].
let data = "abcde".as_bytes().as_ptr();
let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 5) };
assert_eq!(res, 5);
//Two notification should be received.
let expected_event0 = u32::try_from(libc::EPOLLIN | libc::EPOLLOUT).unwrap();
let expected_value0 = fds[0] as u64;
let expected_event1 = u32::try_from(libc::EPOLLOUT).unwrap();
let expected_value1 = fds[1] as u64;
check_epoll_wait::<8>(
epfd,
&[(expected_event0, expected_value0), (expected_event1, expected_value1)],
);
// Read 3 bytes from fds[0].
let mut buf: [u8; 3] = [0; 3];
let res = unsafe { libc::read(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
assert_eq!(res, 3);
assert_eq!(buf, "abc".as_bytes());
// Notification will be provided in Miri.
// But in real systems, no notification will be provided here, since Linux prefers to avoid
// wakeups that are likely to lead to only small amounts of data being read/written.
// We make the test work in both cases, thus documenting the difference in behavior.
let expected_event = u32::try_from(libc::EPOLLOUT).unwrap();
let expected_value = fds[1] as u64;
if cfg!(miri) {
check_epoll_wait::<8>(epfd, &[(expected_event, expected_value)]);
} else {
check_epoll_wait::<8>(epfd, &[]);
}
// Read until the buffer is empty.
let mut buf: [u8; 2] = [0; 2];
let res = unsafe { libc::read(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
assert_eq!(res, 2);
assert_eq!(buf, "de".as_bytes());
// Notification will be provided.
// In real system, notification will be provided too.
let expected_event = u32::try_from(libc::EPOLLOUT).unwrap();
let expected_value = fds[1] as u64;
check_epoll_wait::<8>(epfd, &[(expected_event, expected_value)]);
}
// This is to test whether flag that we don't register won't trigger notification.
fn test_no_notification_for_unregister_flag() {
// Create an epoll instance.
let epfd = unsafe { libc::epoll_create1(0) };
assert_ne!(epfd, -1);
// Create a socketpair instance.
let mut fds = [-1, -1];
let res = unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
assert_eq!(res, 0);
// Register fd[0] with EPOLLOUT|EPOLLET.
let mut ev = libc::epoll_event {
events: (libc::EPOLLOUT | libc::EPOLLET) as _,
u64: u64::try_from(fds[0]).unwrap(),
};
let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fds[1], &mut ev) };
assert_eq!(res, 0);
// Write to fd[1].
let data = "abcde".as_bytes().as_ptr();
let res: i32 =
unsafe { libc::write(fds[1], data as *const libc::c_void, 5).try_into().unwrap() };
assert_eq!(res, 5);
// Check result from epoll_wait. Since we didn't register EPOLLIN flag, the notification won't
// contain EPOLLIN even though fds[0] is now readable.
let expected_event = u32::try_from(libc::EPOLLOUT).unwrap();
let expected_value = u64::try_from(fds[0]).unwrap();
check_epoll_wait::<8>(epfd, &[(expected_event, expected_value)]);
}
fn test_epoll_wait_maxevent_zero() {
// Create an epoll instance.
let epfd = unsafe { libc::epoll_create1(0) };
assert_ne!(epfd, -1);
// It is ok to use uninitialised pointer here because it will error out before the
// pointer actually get accessed.
let array_ptr = MaybeUninit::<libc::epoll_event>::uninit().as_mut_ptr();
let res = unsafe { libc::epoll_wait(epfd, array_ptr, 0, 0) };
let e = std::io::Error::last_os_error();
assert_eq!(e.raw_os_error(), Some(libc::EINVAL));
assert_eq!(res, -1);
}
fn test_socketpair_epollerr() {
// Create an epoll instance.
let epfd = unsafe { libc::epoll_create1(0) };
assert_ne!(epfd, -1);
// Create a socketpair instance.
let mut fds = [-1, -1];
let res = unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
assert_eq!(res, 0);
// Write to fd[0]
let data = "abcde".as_bytes().as_ptr();
let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 5) };
assert_eq!(res, 5);
// Close fds[1].
// EPOLLERR will be triggered if we close peer fd that still has data in its read buffer.
let res = unsafe { libc::close(fds[1]) };
assert_eq!(res, 0);
// Register fd[1] with EPOLLIN|EPOLLOUT|EPOLLET|EPOLLRDHUP
let mut ev = libc::epoll_event {
events: (libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET | libc::EPOLLRDHUP) as _,
u64: u64::try_from(fds[1]).unwrap(),
};
let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fds[0], &mut ev) };
assert_ne!(res, -1);
// Check result from epoll_wait.
let expected_event = u32::try_from(
libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLHUP | libc::EPOLLRDHUP | libc::EPOLLERR,
)
.unwrap();
let expected_value = u64::try_from(fds[1]).unwrap();
check_epoll_wait::<8>(epfd, &[(expected_event, expected_value)]);
}
// This is a test for https://github.com/rust-lang/miri/issues/3812,
// epoll can lose events if they don't fit in the output buffer.
fn test_epoll_lost_events() {
// Create an epoll instance.
let epfd = unsafe { libc::epoll_create1(0) };
assert_ne!(epfd, -1);
// Create a socketpair instance.
let mut fds = [-1, -1];
let res = unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
assert_eq!(res, 0);
// Register both fd to the same epoll instance.
let mut ev = libc::epoll_event { events: EPOLL_IN_OUT_ET, u64: fds[0] as u64 };
let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fds[0], &mut ev) };
assert_eq!(res, 0);
let mut ev = libc::epoll_event { events: EPOLL_IN_OUT_ET, u64: fds[1] as u64 };
let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fds[1], &mut ev) };
assert_eq!(res, 0);
//Two notification should be received. But we only provide buffer for one event.
let expected_event0 = u32::try_from(libc::EPOLLOUT).unwrap();
let expected_value0 = fds[0] as u64;
check_epoll_wait::<1>(epfd, &[(expected_event0, expected_value0)]);
// Previous event should be returned for the second epoll_wait.
let expected_event1 = u32::try_from(libc::EPOLLOUT).unwrap();
let expected_value1 = fds[1] as u64;
check_epoll_wait::<1>(epfd, &[(expected_event1, expected_value1)]);
}
// This is testing if closing an fd that is already in ready list will cause an empty entry in
// returned notification.
// Related discussion in https://github.com/rust-lang/miri/pull/3818#discussion_r1720679440.
fn test_ready_list_fetching_logic() {
// Create an epoll instance.
let epfd = unsafe { libc::epoll_create1(0) };
assert_ne!(epfd, -1);
// Create two eventfd instances.
let flags = libc::EFD_NONBLOCK | libc::EFD_CLOEXEC;
let fd0 = unsafe { libc::eventfd(0, flags) };
let fd1 = unsafe { libc::eventfd(0, flags) };
// Register both fd to the same epoll instance. At this point, both of them are on the ready list.
let mut ev = libc::epoll_event { events: EPOLL_IN_OUT_ET, u64: fd0 as u64 };
let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fd0, &mut ev) };
assert_eq!(res, 0);
let mut ev = libc::epoll_event { events: EPOLL_IN_OUT_ET, u64: fd1 as u64 };
let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fd1, &mut ev) };
assert_eq!(res, 0);
// Close fd0 so the first entry in the ready list will be empty.
let res = unsafe { libc::close(fd0) };
assert_eq!(res, 0);
// Notification for fd1 should be returned.
let expected_event1 = u32::try_from(libc::EPOLLOUT).unwrap();
let expected_value1 = fd1 as u64;
check_epoll_wait::<1>(epfd, &[(expected_event1, expected_value1)]);
}

View File

@ -75,11 +75,15 @@ fn test_dlsym() {
assert_eq!(errno, libc::EBADF);
}
fn test_getuid() {
let _val = unsafe { libc::getuid() };
}
fn main() {
test_thread_local_errno();
test_environ();
test_dlsym();
test_getuid();
#[cfg(target_os = "linux")]
test_sigrt();

View File

@ -0,0 +1,99 @@
//@ignore-target-windows: No libc pipe on Windows
// test_race depends on a deterministic schedule.
//@compile-flags: -Zmiri-preemption-rate=0
use std::thread;
fn main() {
test_pipe();
test_pipe_threaded();
test_race();
}
fn test_pipe() {
let mut fds = [-1, -1];
let res = unsafe { libc::pipe(fds.as_mut_ptr()) };
assert_eq!(res, 0);
// Read size == data available in buffer.
let data = "12345".as_bytes().as_ptr();
let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 5) };
assert_eq!(res, 5);
let mut buf3: [u8; 5] = [0; 5];
let res = unsafe { libc::read(fds[0], buf3.as_mut_ptr().cast(), buf3.len() as libc::size_t) };
assert_eq!(res, 5);
assert_eq!(buf3, "12345".as_bytes());
// Read size > data available in buffer.
let data = "123".as_bytes().as_ptr();
let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 3) };
assert_eq!(res, 3);
let mut buf4: [u8; 5] = [0; 5];
let res = unsafe { libc::read(fds[0], buf4.as_mut_ptr().cast(), buf4.len() as libc::size_t) };
assert_eq!(res, 3);
assert_eq!(&buf4[0..3], "123".as_bytes());
}
fn test_pipe_threaded() {
let mut fds = [-1, -1];
let res = unsafe { libc::pipe(fds.as_mut_ptr()) };
assert_eq!(res, 0);
let thread1 = thread::spawn(move || {
let mut buf: [u8; 5] = [0; 5];
let res: i64 = unsafe {
libc::read(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t)
.try_into()
.unwrap()
};
assert_eq!(res, 5);
assert_eq!(buf, "abcde".as_bytes());
});
// FIXME: we should yield here once blocking is implemented.
//thread::yield_now();
let data = "abcde".as_bytes().as_ptr();
let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 5) };
assert_eq!(res, 5);
thread1.join().unwrap();
// Read and write from different direction
let thread2 = thread::spawn(move || {
// FIXME: we should yield here once blocking is implemented.
//thread::yield_now();
let data = "12345".as_bytes().as_ptr();
let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 5) };
assert_eq!(res, 5);
});
// FIXME: we should not yield here once blocking is implemented.
thread::yield_now();
let mut buf: [u8; 5] = [0; 5];
let res = unsafe { libc::read(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
assert_eq!(res, 5);
assert_eq!(buf, "12345".as_bytes());
thread2.join().unwrap();
}
fn test_race() {
static mut VAL: u8 = 0;
let mut fds = [-1, -1];
let res = unsafe { libc::pipe(fds.as_mut_ptr()) };
assert_eq!(res, 0);
let thread1 = thread::spawn(move || {
let mut buf: [u8; 1] = [0; 1];
// write() from the main thread will occur before the read() here
// because preemption is disabled and the main thread yields after write().
let res: i32 = unsafe {
libc::read(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t)
.try_into()
.unwrap()
};
assert_eq!(res, 1);
assert_eq!(buf, "a".as_bytes());
// The read above establishes a happens-before so it is now safe to access this global variable.
unsafe { assert_eq!(VAL, 1) };
});
unsafe { VAL = 1 };
let data = "a".as_bytes().as_ptr();
let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 1) };
assert_eq!(res, 1);
thread::yield_now();
thread1.join().unwrap();
}

View File

@ -10,65 +10,68 @@ fn main() {
fn test_socketpair() {
let mut fds = [-1, -1];
let mut res =
unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
let res = unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
assert_eq!(res, 0);
// Read size == data available in buffer.
let data = "abcde".as_bytes().as_ptr();
res = unsafe { libc::write(fds[0], data as *const libc::c_void, 5).try_into().unwrap() };
let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 5) };
assert_eq!(res, 5);
let mut buf: [u8; 5] = [0; 5];
res = unsafe {
libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t).try_into().unwrap()
};
let res = unsafe { libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
assert_eq!(res, 5);
assert_eq!(buf, "abcde".as_bytes());
// Read size > data available in buffer.
let data = "abc".as_bytes().as_ptr();
res = unsafe { libc::write(fds[0], data as *const libc::c_void, 3).try_into().unwrap() };
let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 3) };
assert_eq!(res, 3);
let mut buf2: [u8; 5] = [0; 5];
res = unsafe {
libc::read(fds[1], buf2.as_mut_ptr().cast(), buf2.len() as libc::size_t).try_into().unwrap()
};
let res = unsafe { libc::read(fds[1], buf2.as_mut_ptr().cast(), buf2.len() as libc::size_t) };
assert_eq!(res, 3);
assert_eq!(&buf2[0..3], "abc".as_bytes());
// Test read and write from another direction.
// Read size == data available in buffer.
let data = "12345".as_bytes().as_ptr();
res = unsafe { libc::write(fds[1], data as *const libc::c_void, 5).try_into().unwrap() };
let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 5) };
assert_eq!(res, 5);
let mut buf3: [u8; 5] = [0; 5];
res = unsafe {
libc::read(fds[0], buf3.as_mut_ptr().cast(), buf3.len() as libc::size_t).try_into().unwrap()
};
let res = unsafe { libc::read(fds[0], buf3.as_mut_ptr().cast(), buf3.len() as libc::size_t) };
assert_eq!(res, 5);
assert_eq!(buf3, "12345".as_bytes());
// Read size > data available in buffer.
let data = "123".as_bytes().as_ptr();
res = unsafe { libc::write(fds[1], data as *const libc::c_void, 3).try_into().unwrap() };
let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 3) };
assert_eq!(res, 3);
let mut buf4: [u8; 5] = [0; 5];
res = unsafe {
libc::read(fds[0], buf4.as_mut_ptr().cast(), buf4.len() as libc::size_t).try_into().unwrap()
};
let res = unsafe { libc::read(fds[0], buf4.as_mut_ptr().cast(), buf4.len() as libc::size_t) };
assert_eq!(res, 3);
assert_eq!(&buf4[0..3], "123".as_bytes());
// Test when happens when we close one end, with some data in the buffer.
let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 3) };
assert_eq!(res, 3);
unsafe { libc::close(fds[0]) };
// Reading the other end should return that data, then EOF.
let mut buf: [u8; 5] = [0; 5];
let res = unsafe { libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
assert_eq!(res, 3);
assert_eq!(&buf[0..3], "123".as_bytes());
let res = unsafe { libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
assert_eq!(res, 0); // 0-sized read: EOF.
// Writing the other end should emit EPIPE.
let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 1) };
assert_eq!(res, -1);
assert_eq!(std::io::Error::last_os_error().raw_os_error(), Some(libc::EPIPE));
}
fn test_socketpair_threaded() {
let mut fds = [-1, -1];
let mut res =
unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
let res = unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
assert_eq!(res, 0);
let data = "abcde".as_bytes().as_ptr();
res = unsafe { libc::write(fds[0], data as *const libc::c_void, 5).try_into().unwrap() };
assert_eq!(res, 5);
let thread1 = thread::spawn(move || {
let mut buf: [u8; 5] = [0; 5];
let res: i64 = unsafe {
@ -79,28 +82,34 @@ fn test_socketpair_threaded() {
assert_eq!(res, 5);
assert_eq!(buf, "abcde".as_bytes());
});
// FIXME: we should yield here once blocking is implemented.
//thread::yield_now();
let data = "abcde".as_bytes().as_ptr();
let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 5) };
assert_eq!(res, 5);
thread1.join().unwrap();
// Read and write from different direction
let thread2 = thread::spawn(move || {
// FIXME: we should yield here once blocking is implemented.
//thread::yield_now();
let data = "12345".as_bytes().as_ptr();
let res: i64 =
unsafe { libc::write(fds[0], data as *const libc::c_void, 5).try_into().unwrap() };
let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 5) };
assert_eq!(res, 5);
});
thread2.join().unwrap();
// FIXME: we should not yield here once blocking is implemented.
thread::yield_now();
let mut buf: [u8; 5] = [0; 5];
res = unsafe {
libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t).try_into().unwrap()
};
let res = unsafe { libc::read(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
assert_eq!(res, 5);
assert_eq!(buf, "12345".as_bytes());
thread2.join().unwrap();
}
fn test_race() {
static mut VAL: u8 = 0;
let mut fds = [-1, -1];
let mut res =
unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
let res = unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
assert_eq!(res, 0);
let thread1 = thread::spawn(move || {
let mut buf: [u8; 1] = [0; 1];
@ -113,11 +122,12 @@ fn test_race() {
};
assert_eq!(res, 1);
assert_eq!(buf, "a".as_bytes());
// The read above establishes a happens-before so it is now safe to access this global variable.
unsafe { assert_eq!(VAL, 1) };
});
unsafe { VAL = 1 };
let data = "a".as_bytes().as_ptr();
res = unsafe { libc::write(fds[0], data as *const libc::c_void, 1).try_into().unwrap() };
let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 1) };
assert_eq!(res, 1);
thread::yield_now();
thread1.join().unwrap();

View File

@ -1,5 +1,4 @@
//@ignore-target-windows: Windows uses a different mechanism for `thread_local!`
#![feature(thread_local)]
#![feature(thread_local, cfg_target_thread_local)]
use std::cell::Cell;
@ -8,16 +7,20 @@ use std::cell::Cell;
//
// The test covers both TLS statics and the TLS macro.
pub fn main() {
thread_local! {
static TLS_KEY: Cell<Option<&'static i32>> = Cell::new(None);
}
TLS_KEY.with(|cell| {
cell.set(Some(Box::leak(Box::new(123))));
});
#[thread_local]
static TLS: Cell<Option<&'static i32>> = Cell::new(None);
TLS.set(Some(Box::leak(Box::new(123))));
// We can only ignore leaks on targets that use `#[thread_local]` statics to implement
// `thread_local!`. Ignore the test on targest that don't.
if cfg!(target_thread_local) {
thread_local! {
static TLS_KEY: Cell<Option<&'static i32>> = Cell::new(None);
}
TLS_KEY.with(|cell| {
cell.set(Some(Box::leak(Box::new(123))));
});
}
}

View File

@ -2,7 +2,7 @@
Warning: this tree is indicative only. Some tags may have been hidden.
0.. 1
| Act | └─┬──<TAG=root of the allocation>
| RsM | └────<TAG=data, x, y>
| ReIM| └────<TAG=data, x, y>
──────────────────────────────────────────────────
──────────────────────────────────────────────────
Warning: this tree is indicative only. Some tags may have been hidden.

View File

@ -2,27 +2,27 @@
Warning: this tree is indicative only. Some tags may have been hidden.
0.. 1
| Act | └─┬──<TAG=root of the allocation>
| Rs | └─┬──<TAG=data>
| Rs | └────<TAG=x>
| Res | └─┬──<TAG=data>
| Res | └────<TAG=x>
──────────────────────────────────────────────────
──────────────────────────────────────────────────
Warning: this tree is indicative only. Some tags may have been hidden.
0.. 1
| Act | └─┬──<TAG=root of the allocation>
| Rs | └─┬──<TAG=data>
| Rs | └─┬──<TAG=x>
| Rs | └─┬──<TAG=caller:x>
| Rs | └────<TAG=callee:x> Strongly protected
| Res | └─┬──<TAG=data>
| Res | └─┬──<TAG=x>
| Res | └─┬──<TAG=caller:x>
| Res | └────<TAG=callee:x> Strongly protected
──────────────────────────────────────────────────
──────────────────────────────────────────────────
Warning: this tree is indicative only. Some tags may have been hidden.
0.. 1
| Act | └─┬──<TAG=root of the allocation>
| Rs | └─┬──<TAG=data>
| Rs | ├─┬──<TAG=x>
| Rs | │ └─┬──<TAG=caller:x>
| Rs | │ └────<TAG=callee:x>
| Rs | └────<TAG=y>
| Res | └─┬──<TAG=data>
| Res | ├─┬──<TAG=x>
| Res | │ └─┬──<TAG=caller:x>
| Res | │ └────<TAG=callee:x>
| Res | └────<TAG=y>
──────────────────────────────────────────────────
──────────────────────────────────────────────────
Warning: this tree is indicative only. Some tags may have been hidden.

View File

@ -2,7 +2,7 @@
Warning: this tree is indicative only. Some tags may have been hidden.
0.. 1.. 2.. 10.. 11.. 100.. 101..1000..1001..1024
| Act | Act | Act | Act | Act | Act | Act | Act | Act | └─┬──<TAG=root of the allocation>
| Rs | Act | Rs | Act | Rs | Act | Rs | Act | Rs | └─┬──<TAG=data, data>
| Res | Act | Res | Act | Res | Act | Res | Act | Res | └─┬──<TAG=data, data>
|-----| Act |-----|?Dis |-----|?Dis |-----|?Dis |-----| ├────<TAG=data[1]>
|-----|-----|-----| Act |-----|?Dis |-----|?Dis |-----| ├────<TAG=data[10]>
|-----|-----|-----|-----|-----| Frz |-----|?Dis |-----| ├────<TAG=data[100]>

View File

@ -11,5 +11,5 @@ Warning: this tree is indicative only. Some tags may have been hidden.
| Act | └─┬──<TAG=root of the allocation>
| Act | └─┬──<TAG=parent>
| Frz | ├────<TAG=x>
| Rs | └────<TAG=y>
| Res | └────<TAG=y>
──────────────────────────────────────────────────

View File

@ -3,20 +3,20 @@
Warning: this tree is indicative only. Some tags may have been hidden.
0.. 1
| Act | └─┬──<TAG=root of the allocation>
| RsM | └─┬──<TAG=base>
| RsM | ├─┬──<TAG=x>
| RsM | │ └─┬──<TAG=caller:x>
| RsC | │ └────<TAG=callee:x>
| RsM | └────<TAG=y, caller:y, callee:y>
| ReIM| └─┬──<TAG=base>
| ReIM| ├─┬──<TAG=x>
| ReIM| │ └─┬──<TAG=caller:x>
| ResC| │ └────<TAG=callee:x>
| ReIM| └────<TAG=y, caller:y, callee:y>
──────────────────────────────────────────────────
[interior mut] Foreign Read: Re* -> Re*
──────────────────────────────────────────────────
Warning: this tree is indicative only. Some tags may have been hidden.
0.. 8
| Act | └─┬──<TAG=root of the allocation>
| RsM | └─┬──<TAG=base>
| RsM | ├────<TAG=x>
| RsM | └────<TAG=y>
| ReIM| └─┬──<TAG=base>
| ReIM| ├────<TAG=x>
| ReIM| └────<TAG=y>
──────────────────────────────────────────────────
[interior mut] Foreign Write: Re* -> Re*
──────────────────────────────────────────────────
@ -24,7 +24,7 @@ Warning: this tree is indicative only. Some tags may have been hidden.
0.. 8
| Act | └─┬──<TAG=root of the allocation>
| Act | └─┬──<TAG=base>
| RsM | ├────<TAG=x>
| ReIM| ├────<TAG=x>
| Act | └────<TAG=y>
──────────────────────────────────────────────────
[protected] Foreign Read: Res -> Frz
@ -32,20 +32,20 @@ Warning: this tree is indicative only. Some tags may have been hidden.
Warning: this tree is indicative only. Some tags may have been hidden.
0.. 1
| Act | └─┬──<TAG=root of the allocation>
| Rs | └─┬──<TAG=base>
| Rs | ├─┬──<TAG=x>
| Rs | │ └─┬──<TAG=caller:x>
| RsC | │ └────<TAG=callee:x>
| Rs | └────<TAG=y, caller:y, callee:y>
| Res | └─┬──<TAG=base>
| Res | ├─┬──<TAG=x>
| Res | │ └─┬──<TAG=caller:x>
| ResC| │ └────<TAG=callee:x>
| Res | └────<TAG=y, caller:y, callee:y>
──────────────────────────────────────────────────
[] Foreign Read: Res -> Res
──────────────────────────────────────────────────
Warning: this tree is indicative only. Some tags may have been hidden.
0.. 1
| Act | └─┬──<TAG=root of the allocation>
| Rs | └─┬──<TAG=base>
| Rs | ├────<TAG=x>
| Rs | └────<TAG=y>
| Res | └─┬──<TAG=base>
| Res | ├────<TAG=x>
| Res | └────<TAG=y>
──────────────────────────────────────────────────
[] Foreign Write: Res -> Dis
──────────────────────────────────────────────────

View File

@ -2,8 +2,8 @@
Warning: this tree is indicative only. Some tags may have been hidden.
0.. 1
| Act | └─┬──<TAG=root of the allocation>
| Rs | └─┬──<TAG=base>
| Rs | └────<TAG=raw, uniq, uniq>
| Res | └─┬──<TAG=base>
| Res | └────<TAG=raw, uniq, uniq>
──────────────────────────────────────────────────
──────────────────────────────────────────────────
Warning: this tree is indicative only. Some tags may have been hidden.

View File

@ -2,8 +2,8 @@
Warning: this tree is indicative only. Some tags may have been hidden.
0.. 1
| Act | └─┬──<TAG=root of the allocation>
| Rs | └─┬──<TAG=base>
| Rs | └─┬──<TAG=raw>
| Res | └─┬──<TAG=base>
| Res | └─┬──<TAG=raw>
|-----| └────<TAG=uniq, uniq>
──────────────────────────────────────────────────
──────────────────────────────────────────────────

View File

@ -2,5 +2,5 @@
Warning: this tree is indicative only. Some tags may have been hidden.
0.. 2
| Act | └─┬──<TAG=root of the allocation>
| Rs | └────<TAG=base.as_ptr(), base.as_ptr(), raw_parts.0, reconstructed.as_ptr(), reconstructed.as_ptr()>
| Res | └────<TAG=base.as_ptr(), base.as_ptr(), raw_parts.0, reconstructed.as_ptr(), reconstructed.as_ptr()>
──────────────────────────────────────────────────

View File

@ -1,5 +1,3 @@
// FIXME: This test is broken since https://github.com/rust-lang/rust/pull/126793,
// possibly related to the additional struct between Vec and Unique.
//@revisions: default uniq
// We disable the GC for this test because it would change what is printed.
//@compile-flags: -Zmiri-tree-borrows -Zmiri-provenance-gc=0
@ -32,20 +30,20 @@ fn main() {
// whether we got the distance correct:
// If the output shows
//
// |- <XYZ: uniq>
// '- <XYZ: uniq>
// ├─ <TAG=base.as_ptr()>
// └─ <TAG=base.as_ptr()>
//
// then `nth_parent` is not big enough.
// The correct value for `nth_parent` should be the minimum
// integer for which the output shows
//
// '- <XYZ: uniq, uniq>
// └─ <TAG=base.as_ptr(), base.as_ptr(), ...>
// )
//
// Ultimately we want pointers obtained through independent
// calls of `as_ptr` to be able to alias, which will probably involve
// a new permission that allows aliasing when there is no protector.
let nth_parent = if cfg!(uniq) { 2 } else { 0 };
let nth_parent = if cfg!(uniq) { 9 } else { 0 };
name!(base.as_ptr()=>nth_parent);
name!(base.as_ptr()=>nth_parent);

View File

@ -2,9 +2,7 @@
Warning: this tree is indicative only. Some tags may have been hidden.
0.. 2
| Act | └─┬──<TAG=root of the allocation>
|-----| ├────<TAG=base.as_ptr()>
|-----| ├────<TAG=base.as_ptr()>
|-----| └─┬──<TAG=raw_parts.0>
|-----| ├────<TAG=reconstructed.as_ptr()>
|-----| └────<TAG=reconstructed.as_ptr()>
|-----| └─┬──<TAG=base.as_ptr(), base.as_ptr()>
|-----| └─┬──<TAG=raw_parts.0>
|-----| └────<TAG=reconstructed.as_ptr(), reconstructed.as_ptr()>
──────────────────────────────────────────────────

View File

@ -13,7 +13,7 @@ use ui_test::{
};
fn miri_path() -> PathBuf {
PathBuf::from(option_env!("MIRI").unwrap_or(env!("CARGO_BIN_EXE_miri")))
PathBuf::from(env::var("MIRI").unwrap_or_else(|_| env!("CARGO_BIN_EXE_miri").into()))
}
fn get_host() -> String {
@ -29,7 +29,7 @@ pub fn flagsplit(flags: &str) -> Vec<String> {
// Build the shared object file for testing native function calls.
fn build_native_lib() -> PathBuf {
let cc = option_env!("CC").unwrap_or("cc");
let cc = env::var("CC").unwrap_or_else(|_| "cc".into());
// Target directory that we can write to.
let so_target_dir =
Path::new(&env::var_os("CARGO_TARGET_DIR").unwrap()).join("miri-native-lib");
@ -84,9 +84,11 @@ fn miri_config(target: &str, path: &str, mode: Mode, with_dependencies: bool) ->
if with_dependencies {
// Set the `cargo-miri` binary, which we expect to be in the same folder as the `miri` binary.
// (It's a separate crate, so we don't get an env var from cargo.)
let mut prog = miri_path();
prog.set_file_name("cargo-miri");
config.dependency_builder.program = prog;
config.dependency_builder.program = {
let mut prog = miri_path();
prog.set_file_name(format!("cargo-miri{}", env::consts::EXE_SUFFIX));
prog
};
let builder_args = ["miri", "run"]; // There is no `cargo miri build` so we just use `cargo miri run`.
config.dependency_builder.args = builder_args.into_iter().map(Into::into).collect();
config.dependencies_crate_manifest_path =