Auto merge of #114560 - RalfJung:miri, r=RalfJung

update Miri
This commit is contained in:
bors 2023-08-07 07:25:51 +00:00
commit adb15a20ac
60 changed files with 1645 additions and 778 deletions

View File

@ -130,7 +130,7 @@ jobs:
- name: clippy
run: ./miri clippy -- -D warnings
- name: rustdoc
run: RUSTDOCFLAGS="-Dwarnings" cargo doc --document-private-items
run: RUSTDOCFLAGS="-Dwarnings" ./miri cargo doc --document-private-items
# These jobs doesn't actually test anything, but they're only used to tell
# bors the build completed, as there is no practical way to detect when a

View File

@ -107,7 +107,7 @@ evaluation error was originally raised.
### UI testing
We use ui-testing in Miri, meaning we generate `.stderr` and `.stdout` files for the output
produced by Miri. You can use `./miri bless` to automatically (re)generate these files when
produced by Miri. You can use `./miri test --bless` to automatically (re)generate these files when
you add new tests or change how Miri presents certain output.
Note that when you also use `MIRIFLAGS` to change optimizations and similar, the ui output

View File

@ -56,7 +56,7 @@ fn main() {
return;
}
// The way rustdoc invokes rustc is indistuingishable from the way cargo invokes rustdoc by the
// The way rustdoc invokes rustc is indistinguishable from the way cargo invokes rustdoc by the
// arguments alone. `phase_cargo_rustdoc` sets this environment variable to let us disambiguate.
if env::var_os("MIRI_CALLED_FROM_RUSTDOC").is_some() {
// ...however, we then also see this variable when rustdoc invokes us as the testrunner!

View File

@ -94,7 +94,7 @@ pub fn phase_cargo_miri(mut args: impl Iterator<Item = String>) {
let target = target.as_ref().unwrap_or(host);
// We always setup.
setup(&subcommand, target, &rustc_version, verbose);
let miri_sysroot = setup(&subcommand, target, &rustc_version, verbose);
// Invoke actual cargo for the job, but with different flags.
// We re-use `cargo test` and `cargo run`, which makes target and binary handling very easy but
@ -159,6 +159,8 @@ pub fn phase_cargo_miri(mut args: impl Iterator<Item = String>) {
// Forward all further arguments (not consumed by `ArgSplitFlagValue`) to cargo.
cmd.args(args);
// Let it know where the Miri sysroot lives.
cmd.env("MIRI_SYSROOT", miri_sysroot);
// Set `RUSTC_WRAPPER` to ourselves. Cargo will prepend that binary to its usual invocation,
// i.e., the first argument is `rustc` -- which is what we use in `main` to distinguish
// the two codepaths. (That extra argument is why we prefer this over setting `RUSTC`.)
@ -519,7 +521,7 @@ pub fn phase_runner(mut binary_args: impl Iterator<Item = String>, phase: Runner
// `.rmeta`.
// We also need to remove `--error-format` as cargo specifies that to be JSON,
// but when we run here, cargo does not interpret the JSON any more. `--json`
// then also nees to be dropped.
// then also needs to be dropped.
let mut args = info.args.into_iter();
let error_format_flag = "--error-format";
let json_flag = "--json";
@ -538,8 +540,7 @@ pub fn phase_runner(mut binary_args: impl Iterator<Item = String>, phase: Runner
}
// Respect `MIRIFLAGS`.
if let Ok(a) = env::var("MIRIFLAGS") {
// This code is taken from `RUSTFLAGS` handling in cargo.
let args = a.split(' ').map(str::trim).filter(|s| !s.is_empty()).map(str::to_string);
let args = flagsplit(&a);
cmd.args(args);
}

View File

@ -13,13 +13,20 @@ use crate::util::*;
/// Performs the setup required to make `cargo miri` work: Getting a custom-built libstd. Then sets
/// `MIRI_SYSROOT`. Skipped if `MIRI_SYSROOT` is already set, in which case we expect the user has
/// done all this already.
pub fn setup(subcommand: &MiriCommand, target: &str, rustc_version: &VersionMeta, verbose: usize) {
pub fn setup(
subcommand: &MiriCommand,
target: &str,
rustc_version: &VersionMeta,
verbose: usize,
) -> PathBuf {
let only_setup = matches!(subcommand, MiriCommand::Setup);
let ask_user = !only_setup;
let print_sysroot = only_setup && has_arg_flag("--print-sysroot"); // whether we just print the sysroot path
if !only_setup && std::env::var_os("MIRI_SYSROOT").is_some() {
// Skip setup step if MIRI_SYSROOT is explicitly set, *unless* we are `cargo miri setup`.
return;
if !only_setup {
if let Some(sysroot) = std::env::var_os("MIRI_SYSROOT") {
// Skip setup step if MIRI_SYSROOT is explicitly set, *unless* we are `cargo miri setup`.
return sysroot.into();
}
}
// Determine where the rust sources are located. The env var trumps auto-detection.
@ -92,6 +99,8 @@ pub fn setup(subcommand: &MiriCommand, target: &str, rustc_version: &VersionMeta
command.env("RUSTC", &cargo_miri_path);
}
command.env("MIRI_CALLED_FROM_SETUP", "1");
// Miri expects `MIRI_SYSROOT` to be set when invoked in target mode. Even if that directory is empty.
command.env("MIRI_SYSROOT", &sysroot_dir);
// Make sure there are no other wrappers getting in our way (Cc
// https://github.com/rust-lang/miri/issues/1421,
// https://github.com/rust-lang/miri/issues/2429). Looks like setting
@ -105,7 +114,7 @@ pub fn setup(subcommand: &MiriCommand, target: &str, rustc_version: &VersionMeta
command.arg("-v");
}
} else {
// Supress output.
// Suppress output.
command.stdout(process::Stdio::null());
command.stderr(process::Stdio::null());
}
@ -117,8 +126,6 @@ pub fn setup(subcommand: &MiriCommand, target: &str, rustc_version: &VersionMeta
// the user might have set, which is consistent with normal `cargo build` that does
// not apply `RUSTFLAGS` to the sysroot either.
let rustflags = &["-Cdebug-assertions=off", "-Coverflow-checks=on"];
// Make sure all target-level Miri invocations know their sysroot.
std::env::set_var("MIRI_SYSROOT", &sysroot_dir);
// Do the build.
if print_sysroot {
@ -159,4 +166,6 @@ pub fn setup(subcommand: &MiriCommand, target: &str, rustc_version: &VersionMeta
// Print just the sysroot and nothing else to stdout; this way we do not need any escaping.
println!("{}", sysroot_dir.display());
}
sysroot_dir
}

View File

@ -1,7 +1,5 @@
use std::collections::HashMap;
use std::env;
use std::ffi::OsString;
use std::fmt::Write as _;
use std::fs::File;
use std::io::{self, BufWriter, Read, Write};
use std::ops::Not;
@ -114,6 +112,11 @@ pub fn cargo() -> Command {
Command::new(env::var_os("CARGO").unwrap_or_else(|| OsString::from("cargo")))
}
pub fn flagsplit(flags: &str) -> Vec<String> {
// This code is taken from `RUSTFLAGS` handling in cargo.
flags.split(' ').map(str::trim).filter(|s| !s.is_empty()).map(str::to_string).collect()
}
/// Execute the `Command`, where possible by replacing the current process with a new process
/// described by the `Command`. Then exit this process with the exit code of the new process.
pub fn exec(mut cmd: Command) -> ! {
@ -242,46 +245,10 @@ pub fn local_crates(metadata: &Metadata) -> String {
local_crates
}
fn env_vars_from_cmd(cmd: &Command) -> Vec<(String, String)> {
let mut envs = HashMap::new();
for (key, value) in std::env::vars() {
envs.insert(key, value);
}
for (key, value) in cmd.get_envs() {
if let Some(value) = value {
envs.insert(key.to_string_lossy().to_string(), value.to_string_lossy().to_string());
} else {
envs.remove(&key.to_string_lossy().to_string());
}
}
let mut envs: Vec<_> = envs.into_iter().collect();
envs.sort();
envs
}
/// Debug-print a command that is going to be run.
pub fn debug_cmd(prefix: &str, verbose: usize, cmd: &Command) {
if verbose == 0 {
return;
}
// We only do a single `eprintln!` call to minimize concurrency interactions.
let mut out = prefix.to_string();
writeln!(out, " running command: env \\").unwrap();
if verbose > 1 {
// Print the full environment this will be called in.
for (key, value) in env_vars_from_cmd(cmd) {
writeln!(out, "{key}={value:?} \\").unwrap();
}
} else {
// Print only what has been changed for this `cmd`.
for (var, val) in cmd.get_envs() {
if let Some(val) = val {
writeln!(out, "{}={val:?} \\", var.to_string_lossy()).unwrap();
} else {
writeln!(out, "--unset={}", var.to_string_lossy()).unwrap();
}
}
}
write!(out, "{cmd:?}").unwrap();
eprintln!("{out}");
eprintln!("{prefix} running command: {cmd:?}");
}

View File

@ -1,359 +1,6 @@
#!/bin/bash
set -e
USAGE=$(cat <<"EOF"
COMMANDS
./miri install <flags>:
Installs the miri driver and cargo-miri. <flags> are passed to `cargo
install`. Sets up the rpath such that the installed binary should work in any
working directory. Note that the binaries are placed in the `miri` toolchain
sysroot, to prevent conflicts with other toolchains.
./miri build <flags>:
Just build miri. <flags> are passed to `cargo build`.
./miri check <flags>:
Just check miri. <flags> are passed to `cargo check`.
./miri test <flags>:
Build miri, set up a sysroot and then run the test suite. <flags> are passed
to the final `cargo test` invocation.
./miri run <flags>:
Build miri, set up a sysroot and then run the driver with the given <flags>.
(Also respects MIRIFLAGS environment variable.)
./miri fmt <flags>:
Format all sources and tests. <flags> are passed to `rustfmt`.
./miri clippy <flags>:
Runs clippy on all sources. <flags> are passed to `cargo clippy`.
./miri cargo <flags>:
Runs just `cargo <flags>` with the Miri-specific environment variables.
Mainly meant to be invoked by rust-analyzer.
./miri many-seeds <command>:
Runs <command> over and over again with different seeds for Miri. The MIRIFLAGS
variable is set to its original value appended with ` -Zmiri-seed=$SEED` for
many different seeds. The MIRI_SEEDS variable controls how many seeds are being
tried; MIRI_SEED_START controls the first seed to try.
./miri bench <benches>:
Runs the benchmarks from bench-cargo-miri in hyperfine. hyperfine needs to be installed.
<benches> can explicitly list the benchmarks to run; by default, all of them are run.
./miri toolchain <flags>:
Update and activate the rustup toolchain 'miri' to the commit given in the
`rust-version` file.
`rustup-toolchain-install-master` must be installed for this to work. Any extra
flags are passed to `rustup-toolchain-install-master`.
./miri rustc-pull <commit>:
Pull and merge Miri changes from the rustc repo. Defaults to fetching the latest
rustc commit. The fetched commit is stored in the `rust-version` file, so the
next `./miri toolchain` will install the rustc that just got pulled.
./miri rustc-push <github user> <branch>:
Push Miri changes back to the rustc repo. This will pull a copy of the rustc
history into the Miri repo, unless you set the RUSTC_GIT env var to an existing
clone of the rustc repo.
ENVIRONMENT VARIABLES
MIRI_SYSROOT:
If already set, the "sysroot setup" step is skipped.
CARGO_EXTRA_FLAGS:
Pass extra flags to all cargo invocations. (Ignored by `./miri cargo`.)
EOF
)
## We need to know which command to run and some global constants.
COMMAND="$1"
if [ -z "$COMMAND" ]; then
echo "$USAGE"
exit 1
fi
shift
# macOS does not have a useful readlink/realpath so we have to use Python instead...
MIRIDIR=$(python3 -c 'import pathlib, sys; print(pathlib.Path(sys.argv[1]).resolve().parent.as_posix())' "$0")
# Used for rustc syncs.
JOSH_FILTER=":rev(75dd959a3a40eb5b4574f8d2e23aa6efbeb33573:prefix=src/tools/miri):/src/tools/miri"
# Needed for `./miri bench`.
TOOLCHAIN=$(cd "$MIRIDIR"; rustup show active-toolchain | head -n 1 | cut -d ' ' -f 1)
## Early commands, that don't do auto-things and don't want the environment-altering things happening below.
case "$COMMAND" in
toolchain)
cd "$MIRIDIR"
NEW_COMMIT=$(cat rust-version)
# Make sure rustup-toolchain-install-master is installed.
if ! which rustup-toolchain-install-master >/dev/null; then
echo "Please install rustup-toolchain-install-master by running 'cargo install rustup-toolchain-install-master'"
exit 1
fi
# Check if we already are at that commit.
CUR_COMMIT=$(rustc +miri --version -v 2>/dev/null | grep "^commit-hash: " | cut -d " " -f 2)
if [[ "$CUR_COMMIT" == "$NEW_COMMIT" ]]; then
echo "miri toolchain is already at commit $CUR_COMMIT."
if [[ "$TOOLCHAIN" != "miri" ]]; then
rustup override set miri
fi
exit 0
fi
# Install and setup new toolchain.
rustup toolchain uninstall miri
rustup-toolchain-install-master -n miri -c cargo -c rust-src -c rustc-dev -c llvm-tools -c rustfmt -c clippy "$@" -- "$NEW_COMMIT"
rustup override set miri
# Cleanup.
cargo clean
# Call 'cargo metadata' on the sources in case that changes the lockfile
# (which fails under some setups when it is done from inside vscode).
cargo metadata --format-version 1 --manifest-path "$(rustc --print sysroot)/lib/rustlib/rustc-src/rust/compiler/rustc/Cargo.toml" >/dev/null
# Done!
exit 0
;;
rustc-pull)
cd "$MIRIDIR"
FETCH_COMMIT="$1"
if [ -z "$FETCH_COMMIT" ]; then
FETCH_COMMIT=$(git ls-remote https://github.com/rust-lang/rust/ HEAD | cut -f 1)
fi
# 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.
echo "$FETCH_COMMIT" > rust-version
git commit rust-version -m "Preparing for merge from rustc" || (echo "FAILED to commit rust-version file, something went wrong"; exit 1)
# Fetch given rustc commit and note down which one that was
git fetch http://localhost:8000/rust-lang/rust.git@$FETCH_COMMIT$JOSH_FILTER.git || (echo "FAILED to fetch new commits, something went wrong"; exit 1)
git merge FETCH_HEAD --no-ff -m "Merge from rustc" || (echo "FAILED to merge new commits ($(git rev-parse FETCH_HEAD)), something went wrong"; exit 1)
exit 0
;;
rustc-push)
USER="$1"
BRANCH="$2"
if [ -z "$USER" ] || [ -z "$BRANCH" ]; then
echo "Usage: $0 rustc-push <github user> <branch>"
exit 1
fi
if [ -n "$RUSTC_GIT" ]; then
# Use an existing fork for the branch updates.
cd "$RUSTC_GIT"
else
# Do this in the local Miri repo.
echo "This will pull a copy of the rust-lang/rust history into this Miri checkout, growing it by about 1GB."
read -r -p "To avoid that, abort now and set the RUSTC_GIT environment variable to an existing rustc checkout. Proceed? [y/N] "
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
cd "$MIRIDIR"
fi
# Prepare the branch. Pushing works much better if we use as base exactly
# the commit that we pulled from last time, so we use the `rust-version`
# file as a good approximation of that.
BASE=$(cat "$MIRIDIR/rust-version")
echo "Preparing $USER/rust (base: $BASE)..."
if git fetch "https://github.com/$USER/rust" "$BRANCH" &>/dev/null; then
echo "The branch '$BRANCH' seems to already exist in 'https://github.com/$USER/rust'. Please delete it and try again."
exit 1
fi
git fetch https://github.com/rust-lang/rust $BASE
git push https://github.com/$USER/rust $BASE:refs/heads/$BRANCH -f
echo
# Do the actual push.
cd "$MIRIDIR"
echo "Pushing Miri changes..."
git push http://localhost:8000/$USER/rust.git$JOSH_FILTER.git HEAD:$BRANCH
# Do a round-trip check to make sure the push worked as expected.
echo
git fetch http://localhost:8000/$USER/rust.git@$JOSH_FILTER.git $BRANCH &>/dev/null
if [[ $(git rev-parse HEAD) != $(git rev-parse FETCH_HEAD) ]]; then
echo "ERROR: Josh created a non-roundtrip push! Do NOT merge this into rustc!"
exit 1
else
echo "Confirmed that the push round-trips back to Miri properly. Please create a rustc PR:"
echo " https://github.com/$USER/rust/pull/new/$BRANCH"
exit 0
fi
;;
many-seeds)
MIRI_SEED_START=${MIRI_SEED_START:-0} # default to 0
MIRI_SEEDS=${MIRI_SEEDS:-256} # default to 256
for SEED in $(seq $MIRI_SEED_START $(( $MIRI_SEED_START + $MIRI_SEEDS - 1 )) ); do
echo "Trying seed: $SEED"
MIRIFLAGS="$MIRIFLAGS -Zlayout-seed=$SEED -Zmiri-seed=$SEED" $@ || { echo "Failing seed: $SEED"; break; }
done
exit 0
;;
bench)
# The hyperfine to use
HYPERFINE=${HYPERFINE:-hyperfine -w 1 -m 5 --shell=none}
# Make sure we have an up-to-date Miri installed
"$0" install
# Run the requested benchmarks
if [ -z "${1+exists}" ]; then
BENCHES=( $(ls "$MIRIDIR/bench-cargo-miri" ) )
else
BENCHES=("$@")
fi
for BENCH in "${BENCHES[@]}"; do
$HYPERFINE "cargo +$TOOLCHAIN miri run --manifest-path $MIRIDIR/bench-cargo-miri/$BENCH/Cargo.toml"
done
exit 0
;;
esac
## Run the auto-things.
if [ -z "$MIRI_AUTO_OPS" ]; then
export MIRI_AUTO_OPS=42
# Run this first, so that the toolchain doesn't change after
# other code has run.
if [ -f "$MIRIDIR/.auto-everything" ] || [ -f "$MIRIDIR/.auto-toolchain" ] ; then
$0 toolchain
# Let's make sure to actually use that toolchain, too.
TOOLCHAIN=miri
fi
if [ -f "$MIRIDIR/.auto-everything" ] || [ -f "$MIRIDIR/.auto-fmt" ] ; then
$0 fmt
fi
if [ -f "$MIRIDIR/.auto-everything" ] || [ -f "$MIRIDIR/.auto-clippy" ] ; then
$0 clippy -- -D warnings
fi
fi
## Prepare the environment
# Determine some toolchain properties
TARGET=$(rustc +$TOOLCHAIN --version --verbose | grep "^host:" | cut -d ' ' -f 2)
SYSROOT=$(rustc +$TOOLCHAIN --print sysroot)
LIBDIR=$SYSROOT/lib/rustlib/$TARGET/lib
if ! test -d "$LIBDIR"; then
echo "Something went wrong determining the library dir."
echo "I got $LIBDIR but that does not exist."
echo "Please report a bug at https://github.com/rust-lang/miri/issues."
exit 2
fi
# Prepare flags for cargo and rustc.
CARGO="cargo +$TOOLCHAIN"
# Share target dir between `miri` and `cargo-miri`.
if [ -z "$CARGO_TARGET_DIR" ]; then
export CARGO_TARGET_DIR="$MIRIDIR/target"
fi
# We configure dev builds to not be unusably slow.
if [ -z "$CARGO_PROFILE_DEV_OPT_LEVEL" ]; then
export CARGO_PROFILE_DEV_OPT_LEVEL=2
fi
# Enable rustc-specific lints (ignored without `-Zunstable-options`).
export RUSTFLAGS="-Zunstable-options -Wrustc::internal -Wrust_2018_idioms -Wunused_lifetimes -Wsemicolon_in_expressions_from_macros $RUSTFLAGS"
# We set the rpath so that Miri finds the private rustc libraries it needs.
export RUSTFLAGS="-C link-args=-Wl,-rpath,$LIBDIR $RUSTFLAGS"
## Helper functions
# Build a sysroot and set MIRI_SYSROOT to use it. Arguments are passed to `cargo miri setup`.
build_sysroot() {
if ! MIRI_SYSROOT="$($CARGO run $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/cargo-miri/Cargo.toml -- miri setup --print-sysroot "$@")"; then
# Run it again so the user can see the error.
$CARGO run $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/cargo-miri/Cargo.toml -- miri setup "$@"
echo "'cargo miri setup' failed"
exit 1
fi
export MIRI_SYSROOT
}
# Prepare and set MIRI_SYSROOT. Respects `MIRI_TEST_TARGET` and takes into account
# locally built vs. distributed rustc.
find_sysroot() {
if [ -n "$MIRI_SYSROOT" ]; then
# Sysroot already set, use that.
return 0
fi
# We need to build a sysroot.
if [ -n "$MIRI_TEST_TARGET" ]; then
build_sysroot --target "$MIRI_TEST_TARGET"
else
build_sysroot
fi
}
## Main
# Run command.
case "$COMMAND" in
install)
# Install binaries to the miri toolchain's sysroot so they do not interact with other toolchains.
$CARGO install $CARGO_EXTRA_FLAGS --path "$MIRIDIR" --force --root "$SYSROOT" "$@"
$CARGO install $CARGO_EXTRA_FLAGS --path "$MIRIDIR"/cargo-miri --force --root "$SYSROOT" "$@"
;;
check)
# Check, and let caller control flags.
$CARGO check $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml --all-targets "$@"
$CARGO check $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/cargo-miri/Cargo.toml "$@"
;;
build)
# Build, and let caller control flags.
$CARGO build $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml "$@"
$CARGO build $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/cargo-miri/Cargo.toml "$@"
;;
test|bless)
# First build and get a sysroot.
$CARGO build $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml
find_sysroot
if [ "$COMMAND" = "bless" ]; then
export RUSTC_BLESS="Gesundheit"
fi
# Then test, and let caller control flags.
# Only in root project as `cargo-miri` has no tests.
$CARGO test $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml "$@"
;;
run|run-dep)
# Scan for "--target" to overwrite the "MIRI_TEST_TARGET" env var so
# that we set the MIRI_SYSROOT up the right way.
FOUND_TARGET_OPT=0
for ARG in "$@"; do
if [ "$LAST_ARG" = "--target" ]; then
# Found it!
export MIRI_TEST_TARGET="$ARG"
FOUND_TARGET_OPT=1
break
fi
LAST_ARG="$ARG"
done
if [ "$FOUND_TARGET_OPT" = "0" ] && [ -n "$MIRI_TEST_TARGET" ]; then
# Make sure Miri actually uses this target.
MIRIFLAGS="$MIRIFLAGS --target $MIRI_TEST_TARGET"
fi
CARGO="$CARGO --quiet"
# First build and get a sysroot.
$CARGO build $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml
find_sysroot
# Then run the actual command.
if [ "$COMMAND" = "run-dep" ]; then
exec $CARGO test --test compiletest $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml -- --miri-run-dep-mode $MIRIFLAGS "$@"
else
exec $CARGO run $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml -- $MIRIFLAGS "$@"
fi
;;
fmt)
find "$MIRIDIR" -not \( -name target -prune \) -name '*.rs' \
| xargs rustfmt +$TOOLCHAIN --edition=2021 --config-path "$MIRIDIR/rustfmt.toml" "$@"
;;
clippy)
$CARGO clippy $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml --all-targets "$@"
$CARGO clippy $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/cargo-miri/Cargo.toml "$@"
;;
cargo)
# We carefully kept the working dir intact, so this will run cargo *on the workspace in the
# current working dir*, not on the main Miri workspace. That is exactly what RA needs.
$CARGO "$@"
;;
*)
echo "Unknown command: $COMMAND"
exit 1
;;
esac
# 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.
cargo build -q --manifest-path "$(dirname "$0")"/miri-script/Cargo.toml
"$(dirname "$0")"/miri-script/target/debug/miri-script "$@"

View File

@ -0,0 +1,160 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "anyhow"
version = "1.0.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
[[package]]
name = "dunce"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b"
[[package]]
name = "either"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
[[package]]
name = "itertools"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
"either",
]
[[package]]
name = "libc"
version = "0.2.144"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1"
[[package]]
name = "miri-script"
version = "0.1.0"
dependencies = [
"anyhow",
"dunce",
"itertools",
"path_macro",
"rustc_version",
"shell-words",
"walkdir",
"which",
"xshell",
]
[[package]]
name = "once_cell"
version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "path_macro"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6e819bbd49d5939f682638fa54826bf1650abddcd65d000923de8ad63cc7d15"
[[package]]
name = "rustc_version"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"semver",
]
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "semver"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
[[package]]
name = "shell-words"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
[[package]]
name = "walkdir"
version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "which"
version = "4.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269"
dependencies = [
"either",
"libc",
"once_cell",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "xshell"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "962c039b3a7b16cf4e9a4248397c6585c07547412e7d6a6e035389a802dcfe90"
dependencies = [
"xshell-macros",
]
[[package]]
name = "xshell-macros"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dbabb1cbd15a1d6d12d9ed6b35cc6777d4af87ab3ba155ea37215f20beab80c"

View File

@ -0,0 +1,22 @@
[package]
authors = ["Miri Team"]
description = "Helpers for miri maintenance"
license = "MIT OR Apache-2.0"
name = "miri-script"
repository = "https://github.com/rust-lang/miri"
version = "0.1.0"
default-run = "miri-script"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
which = "4.4"
walkdir = "2.3"
itertools = "0.10"
path_macro = "1.0"
shell-words = "1.1"
anyhow = "1.0"
xshell = "0.2"
rustc_version = "0.4"
dunce = "1.0.4"

View File

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

View File

@ -0,0 +1,468 @@
use std::env;
use std::ffi::OsString;
use std::io::Write;
use std::ops::Not;
use anyhow::{anyhow, bail, Context, Result};
use path_macro::path;
use walkdir::WalkDir;
use xshell::{cmd, Shell};
use crate::util::*;
use crate::Command;
/// Used for rustc syncs.
const JOSH_FILTER: &str =
":rev(75dd959a3a40eb5b4574f8d2e23aa6efbeb33573:prefix=src/tools/miri):/src/tools/miri";
impl MiriEnv {
fn build_miri_sysroot(&mut self, quiet: bool) -> Result<()> {
if self.sh.var("MIRI_SYSROOT").is_ok() {
// Sysroot already set, use that.
return Ok(());
}
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)?;
let target = &match self.sh.var("MIRI_TEST_TARGET") {
Ok(target) => vec!["--target".into(), target],
Err(_) => vec![],
};
if !quiet {
eprintln!("$ (building Miri sysroot)");
}
let output = cmd!(self.sh,
"cargo +{toolchain} --quiet run {cargo_extra_flags...} --manifest-path {manifest_path} --
miri setup --print-sysroot {target...}"
).read();
let Ok(output) = output else {
// Run it again (without `--print-sysroot` or `--quiet`) so the user can see the error.
cmd!(
self.sh,
"cargo +{toolchain} run {cargo_extra_flags...} --manifest-path {manifest_path} --
miri setup {target...}"
)
.run()
.with_context(|| "`cargo miri setup` failed")?;
panic!("`cargo miri setup` didn't fail again the 2nd time?");
};
self.sh.set_var("MIRI_SYSROOT", output);
Ok(())
}
}
impl Command {
fn auto_actions() -> Result<()> {
let miri_dir = miri_dir()?;
let auto_everything = path!(miri_dir / ".auto-everything").exists();
let auto_toolchain = auto_everything || path!(miri_dir / ".auto-toolchain").exists();
let auto_fmt = auto_everything || path!(miri_dir / ".auto-fmt").exists();
let auto_clippy = auto_everything || path!(miri_dir / ".auto-clippy").exists();
// `toolchain` goes first as it could affect the others
if auto_toolchain {
Self::toolchain(vec![])?;
}
if auto_fmt {
Self::fmt(vec![])?;
}
if auto_clippy {
Self::clippy(vec![])?;
}
Ok(())
}
pub fn exec(self) -> Result<()> {
match &self {
Command::Install { .. }
| Command::Build { .. }
| Command::Check { .. }
| Command::Test { .. }
| Command::Run { .. }
| Command::Fmt { .. }
| Command::Clippy { .. }
| Command::Cargo { .. } => Self::auto_actions()?,
| Command::ManySeeds { .. }
| Command::Toolchain { .. }
| Command::RustcPull { .. }
| Command::Bench { .. }
| Command::RustcPush { .. } => {}
}
match self {
Command::Install { flags } => Self::install(flags),
Command::Build { flags } => Self::build(flags),
Command::Check { flags } => Self::check(flags),
Command::Test { bless, flags } => Self::test(bless, flags),
Command::Run { dep, flags } => Self::run(dep, flags),
Command::Fmt { flags } => Self::fmt(flags),
Command::Clippy { flags } => Self::clippy(flags),
Command::Cargo { flags } => Self::cargo(flags),
Command::ManySeeds { command } => Self::many_seeds(command),
Command::Bench { benches } => Self::bench(benches),
Command::Toolchain { flags } => Self::toolchain(flags),
Command::RustcPull { commit } => Self::rustc_pull(commit.clone()),
Command::RustcPush { github_user, branch } => Self::rustc_push(github_user, branch),
}
}
fn toolchain(flags: Vec<OsString>) -> Result<()> {
// Make sure rustup-toolchain-install-master is installed.
which::which("rustup-toolchain-install-master")
.context("Please install rustup-toolchain-install-master by running 'cargo install rustup-toolchain-install-master'")?;
let sh = Shell::new()?;
sh.change_dir(miri_dir()?);
let new_commit = Some(sh.read_file("rust-version")?.trim().to_owned());
let current_commit = {
let rustc_info = cmd!(sh, "rustc +miri --version -v").read();
if rustc_info.is_err() {
None
} else {
let metadata = rustc_version::version_meta_for(&rustc_info.unwrap())?;
Some(
metadata
.commit_hash
.ok_or_else(|| anyhow!("rustc metadata did not contain commit hash"))?,
)
}
};
// Check if we already are at that commit.
if current_commit == new_commit {
if active_toolchain()? != "miri" {
cmd!(sh, "rustup override set miri").run()?;
}
return Ok(());
}
// Install and setup new toolchain.
cmd!(sh, "rustup toolchain uninstall miri").run()?;
cmd!(sh, "rustup-toolchain-install-master -n miri -c cargo -c rust-src -c rustc-dev -c llvm-tools -c rustfmt -c clippy {flags...} -- {new_commit...}").run()?;
cmd!(sh, "rustup override set miri").run()?;
// Cleanup.
cmd!(sh, "cargo clean").run()?;
// Call `cargo metadata` on the sources in case that changes the lockfile
// (which fails under some setups when it is done from inside vscode).
let sysroot = cmd!(sh, "rustc --print sysroot").read()?;
let sysroot = sysroot.trim();
cmd!(sh, "cargo metadata --format-version 1 --manifest-path {sysroot}/lib/rustlib/rustc-src/rust/compiler/rustc/Cargo.toml").ignore_stdout().run()?;
Ok(())
}
fn rustc_pull(commit: Option<String>) -> Result<()> {
let sh = Shell::new()?;
sh.change_dir(miri_dir()?);
let commit = commit.map(Result::Ok).unwrap_or_else(|| {
let rust_repo_head =
cmd!(sh, "git ls-remote https://github.com/rust-lang/rust/ HEAD").read()?;
rust_repo_head
.split_whitespace()
.next()
.map(|front| front.trim().to_owned())
.ok_or_else(|| anyhow!("Could not obtain Rust repo HEAD from remote."))
})?;
// Make sure the repo is clean.
if cmd!(sh, "git status --untracked-files=no --porcelain").read()?.is_empty().not() {
bail!("working directory must be clean before running `./miri rustc-pull`");
}
// 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.
// We pass `--no-verify` to avoid running git hooks like `./miri fmt` that could in turn
// trigger auto-actions.
sh.write_file("rust-version", &commit)?;
const PREPARING_COMMIT_MESSAGE: &str = "Preparing for merge from rustc";
cmd!(sh, "git commit rust-version --no-verify -m {PREPARING_COMMIT_MESSAGE}")
.run()
.context("FAILED to commit rust-version file, something went wrong")?;
// Fetch given rustc commit.
cmd!(sh, "git fetch http://localhost:8000/rust-lang/rust.git@{commit}{JOSH_FILTER}.git")
.run()
.map_err(|e| {
// Try to un-do the previous `git commit`, to leave the repo in the state we found it it.
cmd!(sh, "git reset --hard HEAD^")
.run()
.expect("FAILED to clean up again after failed `git fetch`, sorry for that");
e
})
.context("FAILED to fetch new commits, something went wrong (committing the rust-version file has been undone)")?;
// Merge the fetched commit.
const MERGE_COMMIT_MESSAGE: &str = "Merge from rustc";
cmd!(sh, "git merge FETCH_HEAD --no-verify --no-ff -m {MERGE_COMMIT_MESSAGE}")
.run()
.context("FAILED to merge new commits, something went wrong")?;
Ok(())
}
fn rustc_push(github_user: String, branch: String) -> Result<()> {
let sh = Shell::new()?;
sh.change_dir(miri_dir()?);
let base = sh.read_file("rust-version")?.trim().to_owned();
// Make sure the repo is clean.
if cmd!(sh, "git status --untracked-files=no --porcelain").read()?.is_empty().not() {
bail!("working directory must be clean before running `./miri rustc-push`");
}
// Find a repo we can do our preparation in.
if let Ok(rustc_git) = env::var("RUSTC_GIT") {
// If rustc_git is `Some`, we'll use an existing fork for the branch updates.
sh.change_dir(rustc_git);
} else {
// Otherwise, do this in the local Miri repo.
println!(
"This will pull a copy of the rust-lang/rust history into this Miri checkout, growing it by about 1GB."
);
print!(
"To avoid that, abort now and set the `--rustc-git` flag to an existing rustc checkout. Proceed? [y/N] "
);
std::io::stdout().flush()?;
let mut answer = String::new();
std::io::stdin().read_line(&mut answer)?;
if answer.trim().to_lowercase() != "y" {
std::process::exit(1);
}
};
// Prepare the branch. Pushing works much better if we use as base exactly
// the commit that we pulled from last time, so we use the `rust-version`
// file as a good approximation of that.
println!("Preparing {github_user}/rust (base: {base})...");
if cmd!(sh, "git fetch https://github.com/{github_user}/rust {branch}")
.ignore_stderr()
.read()
.is_ok()
{
println!(
"The branch '{branch}' seems to already exist in 'https://github.com/{github_user}/rust'. Please delete it and try again."
);
std::process::exit(1);
}
cmd!(sh, "git fetch https://github.com/rust-lang/rust {base}").run()?;
cmd!(sh, "git push https://github.com/{github_user}/rust {base}:refs/heads/{branch}")
.run()?;
println!();
// Do the actual push.
sh.change_dir(miri_dir()?);
println!("Pushing miri changes...");
cmd!(
sh,
"git push http://localhost:8000/{github_user}/rust.git{JOSH_FILTER}.git HEAD:{branch}"
)
.run()?;
println!();
// Do a round-trip check to make sure the push worked as expected.
cmd!(
sh,
"git fetch http://localhost:8000/{github_user}/rust.git{JOSH_FILTER}.git {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 {
bail!("Josh created a non-roundtrip push! Do NOT merge this into rustc!");
}
println!(
"Confirmed that the push round-trips back to Miri properly. Please create a rustc PR:"
);
println!(" https://github.com/{github_user}/rust/pull/new/{branch}");
Ok(())
}
fn many_seeds(command: Vec<OsString>) -> Result<()> {
let seed_start: u64 = env::var("MIRI_SEED_START")
.unwrap_or_else(|_| "0".into())
.parse()
.context("failed to parse MIRI_SEED_START")?;
let seed_count: u64 = env::var("MIRI_SEEDS")
.unwrap_or_else(|_| "256".into())
.parse()
.context("failed to parse MIRI_SEEDS")?;
let seed_end = seed_start + seed_count;
let Some((command_name, trailing_args)) = command.split_first() else {
bail!("expected many-seeds command to be non-empty");
};
let sh = Shell::new()?;
for seed in seed_start..seed_end {
println!("Trying seed: {seed}");
let mut miriflags = env::var_os("MIRIFLAGS").unwrap_or_default();
miriflags.push(format!(" -Zlayout-seed={seed} -Zmiri-seed={seed}"));
let status = cmd!(sh, "{command_name} {trailing_args...}")
.env("MIRIFLAGS", miriflags)
.quiet()
.run();
if status.is_err() {
println!("Failing seed: {seed}");
break;
}
}
Ok(())
}
fn bench(benches: Vec<OsString>) -> Result<()> {
// The hyperfine to use
let hyperfine = env::var("HYPERFINE");
let hyperfine = hyperfine.as_deref().unwrap_or("hyperfine -w 1 -m 5 --shell=none");
let hyperfine = shell_words::split(hyperfine)?;
let Some((program_name, args)) = hyperfine.split_first() else {
bail!("expected HYPERFINE environment variable to be non-empty");
};
// Make sure we have an up-to-date Miri installed and selected the right toolchain.
Self::install(vec![])?;
let sh = Shell::new()?;
sh.change_dir(miri_dir()?);
let benches_dir = "bench-cargo-miri";
let benches = if benches.is_empty() {
sh.read_dir(benches_dir)?
.into_iter()
.filter(|path| path.is_dir())
.map(Into::into)
.collect()
} else {
benches.to_owned()
};
// Run the requested benchmarks
for bench in benches {
let current_bench = path!(benches_dir / bench / "Cargo.toml");
// We don't attempt to escape `current_bench`, but we wrap it in quotes.
// That seems to make Windows CI happy.
cmd!(
sh,
"{program_name} {args...} 'cargo miri run --manifest-path \"'{current_bench}'\"'"
)
.run()?;
}
Ok(())
}
fn install(flags: Vec<OsString>) -> Result<()> {
let e = MiriEnv::new()?;
e.install_to_sysroot(e.miri_dir.clone(), &flags)?;
e.install_to_sysroot(path!(e.miri_dir / "cargo-miri"), &flags)?;
Ok(())
}
fn build(flags: Vec<OsString>) -> 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)?;
Ok(())
}
fn check(flags: Vec<OsString>) -> 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)?;
Ok(())
}
fn clippy(flags: Vec<OsString>) -> 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<OsString>) -> 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()?;
Ok(())
}
fn test(bless: bool, flags: Vec<OsString>) -> Result<()> {
let mut e = MiriEnv::new()?;
// Prepare a sysroot.
e.build_miri_sysroot(/* quiet */ false)?;
// Then test, and let caller control flags.
// Only in root project as `cargo-miri` has no tests.
if bless {
e.sh.set_var("RUSTC_BLESS", "Gesundheit");
}
e.test(path!(e.miri_dir / "Cargo.toml"), &flags)?;
Ok(())
}
fn run(dep: bool, flags: Vec<OsString>) -> Result<()> {
let mut e = MiriEnv::new()?;
// Scan for "--target" to overwrite the "MIRI_TEST_TARGET" env var so
// that we set the MIRI_SYSROOT up the right way.
use itertools::Itertools;
let target = flags.iter().tuple_windows().find(|(first, _)| first == &"--target");
if let Some((_, target)) = target {
// Found it!
e.sh.set_var("MIRI_TEST_TARGET", target);
} else if let Ok(target) = std::env::var("MIRI_TEST_TARGET") {
// Make sure miri actually uses this target.
let miriflags = e.sh.var("MIRIFLAGS").unwrap_or_default();
e.sh.set_var("MIRIFLAGS", format!("{miriflags} --target {target}"));
}
// Prepare a sysroot.
e.build_miri_sysroot(/* quiet */ true)?;
// Then run the actual command.
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;
if dep {
cmd!(
e.sh,
"cargo +{toolchain} --quiet test --test compiletest {extra_flags...} --manifest-path {miri_manifest} -- --miri-run-dep-mode {miri_flags...} {flags...}"
).quiet().run()?;
} else {
cmd!(
e.sh,
"cargo +{toolchain} --quiet run {extra_flags...} --manifest-path {miri_manifest} -- {miri_flags...} {flags...}"
).quiet().run()?;
}
Ok(())
}
fn fmt(flags: Vec<OsString>) -> Result<()> {
let e = MiriEnv::new()?;
let toolchain = &e.toolchain;
let config_path = path!(e.miri_dir / "rustfmt.toml");
let mut cmd = cmd!(
e.sh,
"rustfmt +{toolchain} --edition=2021 --config-path {config_path} {flags...}"
);
eprintln!("$ {cmd} ...");
// Add all the filenames to the command.
// FIXME: `rustfmt` will follow the `mod` statements in these files, so we get a bunch of
// duplicate diffs.
for item in WalkDir::new(&e.miri_dir).into_iter().filter_entry(|entry| {
let name = entry.file_name().to_string_lossy();
let ty = entry.file_type();
if ty.is_file() {
name.ends_with(".rs")
} else {
// dir or symlink. skip `target` and `.git`.
&name != "target" && &name != ".git"
}
}) {
let item = item?;
if item.file_type().is_file() {
cmd = cmd.arg(item.into_path());
}
}
// We want our own error message, repeating the command is too much.
cmd.quiet().run().map_err(|_| anyhow!("`rustfmt` failed"))?;
Ok(())
}
}

View File

@ -0,0 +1,210 @@
mod commands;
mod util;
use std::env;
use std::ffi::OsString;
use anyhow::{anyhow, bail, Result};
#[derive(Clone, Debug)]
pub enum Command {
/// Installs the miri driver and cargo-miri.
/// Sets up the rpath such that the installed binary should work in any
/// working directory. Note that the binaries are placed in the `miri` toolchain
/// sysroot, to prevent conflicts with other toolchains.
Install {
/// Flags that are passed through to `cargo install`.
flags: Vec<OsString>,
},
/// Just build miri.
Build {
/// Flags that are passed through to `cargo build`.
flags: Vec<OsString>,
},
/// Just check miri.
Check {
/// Flags that are passed through to `cargo check`.
flags: Vec<OsString>,
},
/// Build miri, set up a sysroot and then run the test suite.
Test {
bless: bool,
/// Flags that are passed through to `cargo test`.
flags: Vec<OsString>,
},
/// Build miri, set up a sysroot and then run the driver with the given <flags>.
/// (Also respects MIRIFLAGS environment variable.)
Run {
dep: bool,
/// Flags that are passed through to `miri`.
flags: Vec<OsString>,
},
/// Format all sources and tests.
Fmt {
/// Flags that are passed through to `rustfmt`.
flags: Vec<OsString>,
},
/// Runs clippy on all sources.
Clippy {
/// Flags that are passed through to `cargo clippy`.
flags: Vec<OsString>,
},
/// Runs just `cargo <flags>` with the Miri-specific environment variables.
/// Mainly meant to be invoked by rust-analyzer.
Cargo { flags: Vec<OsString> },
/// Runs <command> over and over again with different seeds for Miri. The MIRIFLAGS
/// variable is set to its original value appended with ` -Zmiri-seed=$SEED` for
/// many different seeds.
ManySeeds { command: Vec<OsString> },
/// Runs the benchmarks from bench-cargo-miri in hyperfine. hyperfine needs to be installed.
Bench {
/// List of benchmarks to run. By default all benchmarks are run.
benches: Vec<OsString>,
},
/// Update and activate the rustup toolchain 'miri' to the commit given in the
/// `rust-version` file.
/// `rustup-toolchain-install-master` must be installed for this to work. Any extra
/// flags are passed to `rustup-toolchain-install-master`.
Toolchain { flags: Vec<OsString> },
/// Pull and merge Miri changes from the rustc repo. Defaults to fetching the latest
/// rustc commit. The fetched commit is stored in the `rust-version` file, so the
/// next `./miri toolchain` will install the rustc that just got pulled.
RustcPull { commit: Option<String> },
/// Push Miri changes back to the rustc repo. This will pull a copy of the rustc
/// history into the Miri repo, unless you set the RUSTC_GIT env var to an existing
/// clone of the rustc repo.
RustcPush { github_user: String, branch: String },
}
const HELP: &str = r#" COMMANDS
./miri build <flags>:
Just build miri. <flags> are passed to `cargo build`.
./miri check <flags>:
Just check miri. <flags> are passed to `cargo check`.
./miri test [--bless] <flags>:
Build miri, set up a sysroot and then run the test suite. <flags> are passed
to the final `cargo test` invocation.
./miri run [--dep] <flags>:
Build miri, set up a sysroot and then run the driver with the given <flags>.
(Also respects MIRIFLAGS environment variable.)
./miri fmt <flags>:
Format all sources and tests. <flags> are passed to `rustfmt`.
./miri clippy <flags>:
Runs clippy on all sources. <flags> are passed to `cargo clippy`.
./miri cargo <flags>:
Runs just `cargo <flags>` with the Miri-specific environment variables.
Mainly meant to be invoked by rust-analyzer.
./miri install <flags>:
Installs the miri driver and cargo-miri. <flags> are passed to `cargo
install`. Sets up the rpath such that the installed binary should work in any
working directory. Note that the binaries are placed in the `miri` toolchain
sysroot, to prevent conflicts with other toolchains.
./miri many-seeds <command>:
Runs <command> over and over again with different seeds for Miri. The MIRIFLAGS
variable is set to its original value appended with ` -Zmiri-seed=$SEED` for
many different seeds. The MIRI_SEEDS variable controls how many seeds are being
tried; MIRI_SEED_START controls the first seed to try.
./miri bench <benches>:
Runs the benchmarks from bench-cargo-miri in hyperfine. hyperfine needs to be installed.
<benches> can explicitly list the benchmarks to run; by default, all of them are run.
./miri toolchain <flags>:
Update and activate the rustup toolchain 'miri' to the commit given in the
`rust-version` file.
`rustup-toolchain-install-master` must be installed for this to work. Any extra
flags are passed to `rustup-toolchain-install-master`.
./miri rustc-pull <commit>:
Pull and merge Miri changes from the rustc repo. Defaults to fetching the latest
rustc commit. The fetched commit is stored in the `rust-version` file, so the
next `./miri toolchain` will install the rustc that just got pulled.
./miri rustc-push <github user> <branch>:
Push Miri changes back to the rustc repo. This will pull a copy of the rustc
history into the Miri repo, unless you set the RUSTC_GIT env var to an existing
clone of the rustc repo.
ENVIRONMENT VARIABLES
MIRI_SYSROOT:
If already set, the "sysroot setup" step is skipped.
CARGO_EXTRA_FLAGS:
Pass extra flags to all cargo invocations. (Ignored by `./miri cargo`.)"#;
fn main() -> Result<()> {
// We are hand-rolling our own argument parser, since `clap` can't express what we need
// (https://github.com/clap-rs/clap/issues/5055).
let mut args = env::args_os().peekable();
args.next().unwrap(); // skip program name
let command = match args.next().and_then(|s| s.into_string().ok()).as_deref() {
Some("build") => Command::Build { flags: args.collect() },
Some("check") => Command::Check { flags: args.collect() },
Some("test") => {
let bless = args.peek().is_some_and(|a| a.to_str() == Some("--bless"));
if bless {
// Consume the flag.
args.next().unwrap();
}
Command::Test { bless, flags: args.collect() }
}
Some("run") => {
let dep = args.peek().is_some_and(|a| a.to_str() == Some("--dep"));
if dep {
// Consume the flag.
args.next().unwrap();
}
Command::Run { dep, flags: args.collect() }
}
Some("fmt") => Command::Fmt { flags: args.collect() },
Some("clippy") => Command::Clippy { flags: args.collect() },
Some("cargo") => Command::Cargo { flags: args.collect() },
Some("install") => Command::Install { flags: args.collect() },
Some("many-seeds") => Command::ManySeeds { command: args.collect() },
Some("bench") => Command::Bench { benches: args.collect() },
Some("toolchain") => Command::Toolchain { flags: args.collect() },
Some("rustc-pull") => {
let commit = args.next().map(|a| a.to_string_lossy().into_owned());
if args.next().is_some() {
bail!("Too many arguments for `./miri rustc-pull`");
}
Command::RustcPull { commit }
}
Some("rustc-push") => {
let github_user = args
.next()
.ok_or_else(|| {
anyhow!("Missing first argument for `./miri rustc-push GITHUB_USER BRANCH`")
})?
.to_string_lossy()
.into_owned();
let branch = args
.next()
.ok_or_else(|| {
anyhow!("Missing second argument for `./miri rustc-push GITHUB_USER BRANCH`")
})?
.to_string_lossy()
.into_owned();
if args.next().is_some() {
bail!("Too many arguments for `./miri rustc-push GITHUB_USER BRANCH`");
}
Command::RustcPush { github_user, branch }
}
_ => {
eprintln!("Unknown or missing command. Usage:\n\n{HELP}");
std::process::exit(1);
}
};
command.exec()?;
Ok(())
}

View File

@ -0,0 +1,144 @@
use std::ffi::{OsStr, OsString};
use std::path::PathBuf;
use anyhow::{Context, Result};
use dunce::canonicalize;
use path_macro::path;
use xshell::{cmd, Shell};
pub fn miri_dir() -> std::io::Result<PathBuf> {
const MIRI_SCRIPT_ROOT_DIR: &str = env!("CARGO_MANIFEST_DIR");
Ok(canonicalize(MIRI_SCRIPT_ROOT_DIR)?.parent().unwrap().into())
}
/// Queries the active toolchain for the Miri dir.
pub fn active_toolchain() -> Result<String> {
let sh = Shell::new()?;
sh.change_dir(miri_dir()?);
let stdout = cmd!(sh, "rustup show active-toolchain").read()?;
Ok(stdout.split_whitespace().next().context("Could not obtain active Rust toolchain")?.into())
}
pub fn flagsplit(flags: &str) -> Vec<String> {
// This code is taken from `RUSTFLAGS` handling in cargo.
flags.split(' ').map(str::trim).filter(|s| !s.is_empty()).map(str::to_string).collect()
}
/// Some extra state we track for building Miri, such as the right RUSTFLAGS.
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,
/// Extra flags to pass to cargo.
pub cargo_extra_flags: Vec<String>,
/// The rustc sysroot
pub sysroot: PathBuf,
/// The shell we use.
pub sh: Shell,
}
impl MiriEnv {
pub fn new() -> Result<Self> {
let toolchain = active_toolchain()?;
let sh = Shell::new()?; // we are preserving the current_dir on this one, so paths resolve properly!
let miri_dir = miri_dir()?;
let sysroot = cmd!(sh, "rustc +{toolchain} --print sysroot").read()?.into();
let target_output = cmd!(sh, "rustc +{toolchain} --version --verbose").read()?;
let rustc_meta = rustc_version::version_meta_for(&target_output)?;
let libdir = path!(sysroot / "lib" / "rustlib" / rustc_meta.host / "lib");
// 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.");
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);
// We configure dev builds to not be unusably slow.
let devel_opt_level =
std::env::var_os("CARGO_PROFILE_DEV_OPT_LEVEL").unwrap_or_else(|| "2".into());
sh.set_var("CARGO_PROFILE_DEV_OPT_LEVEL", devel_opt_level);
// Compute rustflags.
let rustflags = {
let mut flags = OsString::new();
// We set the rpath so that Miri finds the private rustc libraries it needs.
flags.push("-C link-args=-Wl,-rpath,");
flags.push(libdir);
// Enable rustc-specific lints (ignored without `-Zunstable-options`).
flags.push(" -Zunstable-options -Wrustc::internal -Wrust_2018_idioms -Wunused_lifetimes -Wsemicolon_in_expressions_from_macros");
// Add user-defined flags.
if let Some(value) = std::env::var_os("RUSTFLAGS") {
flags.push(" ");
flags.push(value);
}
flags
};
sh.set_var("RUSTFLAGS", rustflags);
// 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);
Ok(MiriEnv { miri_dir, toolchain, sh, sysroot, cargo_extra_flags })
}
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;
// Install binaries to the miri toolchain's `sysroot` so they do not interact with other toolchains.
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: &[OsString],
quiet: bool,
) -> Result<()> {
let MiriEnv { toolchain, cargo_extra_flags, .. } = self;
let quiet_flag = if quiet { Some("--quiet") } else { None };
let mut cmd = cmd!(
self.sh,
"cargo +{toolchain} build {cargo_extra_flags...} --manifest-path {manifest_path} {quiet_flag...} {args...}"
);
cmd.set_quiet(quiet);
cmd.run()?;
Ok(())
}
pub fn check(&self, manifest_path: impl AsRef<OsStr>, args: &[OsString]) -> 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()?;
Ok(())
}
pub fn clippy(&self, manifest_path: impl AsRef<OsStr>, args: &[OsString]) -> 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()?;
Ok(())
}
pub fn test(&self, manifest_path: impl AsRef<OsStr>, args: &[OsString]) -> Result<()> {
let MiriEnv { toolchain, cargo_extra_flags, .. } = self;
cmd!(
self.sh,
"cargo +{toolchain} test {cargo_extra_flags...} --manifest-path {manifest_path} {args...}"
)
.run()?;
Ok(())
}
}

View File

@ -1 +1 @@
d150dbb067e66f351a0b33a54e7d4b464ef51e47
fca59ab5f0e7df7d816bed77a32abc0045ebe80b

View File

@ -13,11 +13,7 @@ use log::trace;
use rustc_data_structures::fx::FxHashSet;
use rustc_middle::mir::{Mutability, RetagKind};
use rustc_middle::ty::{
self,
layout::HasParamEnv,
Ty,
};
use rustc_middle::ty::{self, layout::HasParamEnv, Ty};
use rustc_target::abi::{Abi, Align, Size};
use crate::borrow_tracker::{
@ -608,8 +604,7 @@ impl<'mir: 'ecx, 'tcx: 'mir, 'ecx> EvalContextPrivExt<'mir, 'tcx, 'ecx>
{
}
trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'mir, 'tcx> {
/// Returns the `AllocId` the reborrow was done in, if some actual borrow stack manipulation
/// happened.
/// Returns the provenance that should be used henceforth.
fn sb_reborrow(
&mut self,
place: &MPlaceTy<'tcx, Provenance>,
@ -617,7 +612,7 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
new_perm: NewPermission,
new_tag: BorTag,
retag_info: RetagInfo, // diagnostics info about this retag
) -> InterpResult<'tcx, Option<AllocId>> {
) -> InterpResult<'tcx, Option<Provenance>> {
let this = self.eval_context_mut();
// Ensure we bail out if the pointer goes out-of-bounds (see miri#1050).
this.check_ptr_access_align(place.ptr, size, Align::ONE, CheckInAllocMsg::InboundsTest)?;
@ -699,11 +694,14 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
// pointer tagging for example all calls to get_unchecked on them are invalid.
if let Ok((alloc_id, base_offset, orig_tag)) = this.ptr_try_get_alloc_id(place.ptr) {
log_creation(this, Some((alloc_id, base_offset, orig_tag)))?;
return Ok(Some(alloc_id));
// Still give it the new provenance, it got retagged after all.
return Ok(Some(Provenance::Concrete { alloc_id, tag: new_tag }));
} else {
// This pointer doesn't come with an AllocId. :shrug:
log_creation(this, None)?;
// Provenance unchanged.
return Ok(place.ptr.provenance);
}
// This pointer doesn't come with an AllocId. :shrug:
log_creation(this, None)?;
return Ok(None);
}
let (alloc_id, base_offset, orig_tag) = this.ptr_get_alloc_id(place.ptr)?;
@ -808,7 +806,7 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
}
}
Ok(Some(alloc_id))
Ok(Some(Provenance::Concrete { alloc_id, tag: new_tag }))
}
/// Retags an individual pointer, returning the retagged version.
@ -835,25 +833,10 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
let new_tag = this.machine.borrow_tracker.as_mut().unwrap().get_mut().new_ptr();
// Reborrow.
let alloc_id = this.sb_reborrow(&place, size, new_perm, new_tag, info)?;
let new_prov = this.sb_reborrow(&place, size, new_perm, new_tag, info)?;
// Adjust pointer.
let new_place = place.map_provenance(|p| {
p.map(|prov| {
match alloc_id {
Some(alloc_id) => {
// If `reborrow` could figure out the AllocId of this ptr, hard-code it into the new one.
// Even if we started out with a wildcard, this newly retagged pointer is tied to that allocation.
Provenance::Concrete { alloc_id, tag: new_tag }
}
None => {
// Looks like this has to stay a wildcard pointer.
assert!(matches!(prov, Provenance::Wildcard));
Provenance::Wildcard
}
}
})
});
let new_place = place.map_provenance(|_| new_prov);
// Return new pointer.
Ok(ImmTy::from_immediate(new_place.to_ref(this), val.layout))

View File

@ -218,7 +218,7 @@ impl<'tcx> Tree {
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[derive(Debug, Clone, Copy)]
pub(super) enum TransitionError {
/// This access is not allowed because some parent tag has insufficient permissions.
/// For example, if a tag is `Frozen` and encounters a child write this will

View File

@ -5,11 +5,7 @@ use rustc_target::abi::{Abi, Align, Size};
use crate::borrow_tracker::{AccessKind, GlobalStateInner, ProtectorKind, RetagFields};
use rustc_middle::{
mir::{Mutability, RetagKind},
ty::{
self,
layout::HasParamEnv,
Ty,
},
ty::{self, layout::HasParamEnv, Ty},
};
use rustc_span::def_id::DefId;
@ -121,7 +117,7 @@ impl<'tcx> NewPermission {
let ty_is_freeze = pointee.is_freeze(*cx.tcx, cx.param_env());
let ty_is_unpin = pointee.is_unpin(*cx.tcx, cx.param_env());
let initial_state = match mutability {
Mutability::Mut if ty_is_unpin => Permission::new_unique_2phase(ty_is_freeze),
Mutability::Mut if ty_is_unpin => Permission::new_reserved(ty_is_freeze),
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
@ -150,7 +146,7 @@ impl<'tcx> NewPermission {
let ty_is_freeze = ty.is_freeze(*cx.tcx, cx.param_env());
Self {
zero_size,
initial_state: Permission::new_unique_2phase(ty_is_freeze),
initial_state: Permission::new_reserved(ty_is_freeze),
protector: (kind == RetagKind::FnEntry).then_some(ProtectorKind::WeakProtector),
}
})
@ -165,25 +161,22 @@ impl<'mir: 'ecx, 'tcx: 'mir, 'ecx> EvalContextPrivExt<'mir, 'tcx, 'ecx>
{
}
trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'mir, 'tcx> {
/// Returns the `AllocId` the reborrow was done in, if there is some actual
/// memory associated with this pointer. Returns `None` if there is no actual
/// memory allocated. Also checks that the reborrow of size `ptr_size` is
/// within bounds of the allocation.
///
/// Also returns the tag that the pointer should get, which is essentially
/// `if new_perm.is_some() { new_tag } else { parent_tag }` along with
/// some logging (always) and fake reads (if `new_perm` is
/// `Some(NewPermission { perform_read_access: true }`).
/// Returns the provenance that should be used henceforth.
fn tb_reborrow(
&mut self,
place: &MPlaceTy<'tcx, Provenance>, // parent tag extracted from here
ptr_size: Size,
new_perm: NewPermission,
new_tag: BorTag,
) -> InterpResult<'tcx, Option<(AllocId, BorTag)>> {
) -> InterpResult<'tcx, Option<Provenance>> {
let this = self.eval_context_mut();
// Ensure we bail out if the pointer goes out-of-bounds (see miri#1050).
this.check_ptr_access_align(place.ptr, ptr_size, Align::ONE, CheckInAllocMsg::InboundsTest)?;
this.check_ptr_access_align(
place.ptr,
ptr_size,
Align::ONE,
CheckInAllocMsg::InboundsTest,
)?;
// It is crucial that this gets called on all code paths, to ensure we track tag creation.
let log_creation = |this: &MiriInterpCx<'mir, 'tcx>,
@ -209,7 +202,7 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
// Unlike SB, we *do* a proper retag for size 0 if can identify the allocation.
// After all, the pointer may be lazily initialized outside this initial range.
data
},
}
Err(_) => {
assert_eq!(ptr_size, Size::ZERO); // we did the deref check above, size has to be 0 here
// This pointer doesn't come with an AllocId, so there's no
@ -221,13 +214,14 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
place.layout.ty,
);
log_creation(this, None)?;
return Ok(None);
// Keep original provenance.
return Ok(place.ptr.provenance);
}
};
log_creation(this, Some((alloc_id, base_offset, parent_prov)))?;
let orig_tag = match parent_prov {
ProvenanceExtra::Wildcard => return Ok(None), // TODO: handle wildcard pointers
ProvenanceExtra::Wildcard => return Ok(place.ptr.provenance), // TODO: handle wildcard pointers
ProvenanceExtra::Concrete(tag) => tag,
};
@ -254,31 +248,54 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
.insert(new_tag, protect);
}
let alloc_kind = this.get_alloc_info(alloc_id).2;
if !matches!(alloc_kind, AllocKind::LiveData) {
assert_eq!(ptr_size, Size::ZERO); // we did the deref check above, size has to be 0 here
// There's not actually any bytes here where accesses could even be tracked.
// Just produce the new provenance, nothing else to do.
return Ok(Some(Provenance::Concrete { alloc_id, tag: new_tag }));
}
let span = this.machine.current_span();
let alloc_extra = this.get_alloc_extra(alloc_id)?;
let range = alloc_range(base_offset, ptr_size);
let mut tree_borrows = alloc_extra.borrow_tracker_tb().borrow_mut();
// All reborrows incur a (possibly zero-sized) read access to the parent
{
let global = &this.machine.borrow_tracker.as_ref().unwrap();
let span = this.machine.current_span();
tree_borrows.perform_access(
AccessKind::Read,
orig_tag,
range,
global,
span,
diagnostics::AccessCause::Reborrow,
)?;
tree_borrows.perform_access(
AccessKind::Read,
orig_tag,
range,
this.machine.borrow_tracker.as_ref().unwrap(),
this.machine.current_span(),
diagnostics::AccessCause::Reborrow,
)?;
// Record the parent-child pair in the tree.
tree_borrows.new_child(orig_tag, new_tag, new_perm.initial_state, range, span)?;
drop(tree_borrows);
// Also inform the data race model (but only if any bytes are actually affected).
if range.size.bytes() > 0 {
if let Some(data_race) = alloc_extra.data_race.as_ref() {
data_race.read(alloc_id, range, &this.machine)?;
// We sometimes need to make it a write, since not all retags commute with reads!
// FIXME: Is that truly the semantics we want? Some optimizations are likely to be
// very unhappy without this. We'd tsill ge some UB just by picking a suitable
// interleaving, but wether UB happens can depend on whether a write occurs in the
// future...
let is_write = new_perm.initial_state.is_active()
|| (new_perm.initial_state.is_resrved() && new_perm.protector.is_some());
if is_write {
// Need to get mutable access to alloc_extra.
// (Cannot always do this as we can do read-only reborrowing on read-only allocations.)
let (alloc_extra, machine) = this.get_alloc_extra_mut(alloc_id)?;
alloc_extra.data_race.as_mut().unwrap().write(alloc_id, range, machine)?;
} else {
data_race.read(alloc_id, range, &this.machine)?;
}
}
}
// Record the parent-child pair in the tree.
tree_borrows.new_child(orig_tag, new_tag, new_perm.initial_state, range, span)?;
Ok(Some((alloc_id, new_tag)))
Ok(Some(Provenance::Concrete { alloc_id, tag: new_tag }))
}
/// Retags an individual pointer, returning the retagged version.
@ -314,25 +331,10 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
let new_tag = this.machine.borrow_tracker.as_mut().unwrap().get_mut().new_ptr();
// Compute the actual reborrow.
let reborrowed = this.tb_reborrow(&place, reborrow_size, new_perm, new_tag)?;
let new_prov = this.tb_reborrow(&place, reborrow_size, new_perm, new_tag)?;
// Adjust pointer.
let new_place = place.map_provenance(|p| {
p.map(|prov| {
match reborrowed {
Some((alloc_id, actual_tag)) => {
// If `reborrow` could figure out the AllocId of this ptr, hard-code it into the new one.
// Even if we started out with a wildcard, this newly retagged pointer is tied to that allocation.
Provenance::Concrete { alloc_id, tag: actual_tag }
}
None => {
// Looks like this has to stay a wildcard pointer.
assert!(matches!(prov, Provenance::Wildcard));
Provenance::Wildcard
}
}
})
});
let new_place = place.map_provenance(|_| new_prov);
// Return new pointer.
Ok(ImmTy::from_immediate(new_place.to_ref(this), val.layout))

View File

@ -134,25 +134,32 @@ pub struct PermTransition {
impl Permission {
/// Default initial permission of the root of a new tree.
pub fn new_root() -> Self {
pub fn new_active() -> Self {
Self { inner: Active }
}
/// Default initial permission of a reborrowed mutable reference.
pub fn new_unique_2phase(ty_is_freeze: bool) -> Self {
pub fn new_reserved(ty_is_freeze: bool) -> Self {
Self { inner: Reserved { ty_is_freeze } }
}
/// Default initial permission for return place.
pub fn new_active() -> Self {
Self { inner: Active }
}
/// Default initial permission of a reborrowed shared reference
pub fn new_frozen() -> Self {
Self { inner: Frozen }
}
pub fn is_active(self) -> bool {
matches!(self.inner, Active)
}
pub fn is_resrved(self) -> bool {
matches!(self.inner, Reserved { .. })
}
pub fn is_frozen(self) -> bool {
matches!(self.inner, Frozen)
}
/// Apply the transition to the inner PermissionPriv.
pub fn perform_access(
kind: AccessKind,
@ -438,7 +445,7 @@ mod propagation_optimization_checks {
}
#[test]
fn foreign_read_is_noop_after_write() {
fn foreign_read_is_noop_after_foreign_write() {
use transition::*;
let old_access = AccessKind::Write;
let new_access = AccessKind::Read;

View File

@ -110,7 +110,7 @@ impl LocationState {
// Helper to optimize the tree traversal.
// The optimization here consists of observing thanks to the tests
// `foreign_read_is_noop_after_write` and `all_transitions_idempotent`,
// `foreign_read_is_noop_after_foreign_write` and `all_transitions_idempotent`,
// that there are actually just three possible sequences of events that can occur
// in between two child accesses that produce different results.
//
@ -139,7 +139,7 @@ impl LocationState {
let new_access_noop = match (self.latest_foreign_access, access_kind) {
// Previously applied transition makes the new one a guaranteed
// noop in the two following cases:
// (1) justified by `foreign_read_is_noop_after_write`
// (1) justified by `foreign_read_is_noop_after_foreign_write`
(Some(AccessKind::Write), AccessKind::Read) => true,
// (2) justified by `all_transitions_idempotent`
(Some(old), new) if old == new => true,
@ -376,7 +376,7 @@ where {
impl Tree {
/// Create a new tree, with only a root pointer.
pub fn new(root_tag: BorTag, size: Size, span: Span) -> Self {
let root_perm = Permission::new_root();
let root_perm = Permission::new_active();
let mut tag_mapping = UniKeyMap::default();
let root_idx = tag_mapping.insert(root_tag);
let nodes = {
@ -670,7 +670,8 @@ impl AccessRelatedness {
mod commutation_tests {
use super::*;
impl LocationState {
pub fn all_without_access() -> impl Iterator<Item = Self> {
pub fn all() -> impl Iterator<Item = Self> {
// We keep `latest_foreign_access` at `None` as that's just a cache.
Permission::all().flat_map(|permission| {
[false, true].into_iter().map(move |initialized| {
Self { permission, initialized, latest_foreign_access: None }
@ -695,12 +696,12 @@ mod commutation_tests {
// Any protector state works, but we can't move reads across function boundaries
// so the two read accesses occur under the same protector.
for &protected in &[true, false] {
for loc in LocationState::all_without_access() {
for loc in LocationState::all() {
// 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;
let Ok(_) = loc12.perform_access(kind, rel1, protected) else { continue; };
let Ok(_) = loc12.perform_access(kind, rel2, protected) else { continue; };
let Ok(_) = loc12.perform_access(kind, rel1, protected) else { continue };
let Ok(_) = loc12.perform_access(kind, rel2, protected) else { continue };
// If 1 followed by 2 succeeded, then 2 followed by 1 must also succeed...
let mut loc21 = loc;
@ -718,4 +719,33 @@ mod commutation_tests {
}
}
}
#[test]
#[rustfmt::skip]
// Ensure that of 2 accesses happen, one foreign and one a child, and we are protected, that we
// get UB unless they are both reads.
fn protected_enforces_noalias() {
for rel1 in AccessRelatedness::all() {
for rel2 in AccessRelatedness::all() {
if rel1.is_foreign() == rel2.is_foreign() {
// We want to check pairs of accesses where one is foreign and one is not.
continue;
}
for kind1 in AccessKind::all() {
for kind2 in AccessKind::all() {
for mut state in LocationState::all() {
let protected = true;
let Ok(_) = state.perform_access(kind1, rel1, protected) else { continue };
let Ok(_) = state.perform_access(kind2, rel2, protected) else { continue };
// If these were both allowed, it must have been two reads.
assert!(
kind1 == AccessKind::Read && kind2 == AccessKind::Read,
"failed to enforce noalias between two accesses that are not both reads"
);
}
}
}
}
}
}
}

View File

@ -337,7 +337,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
/// Call a function: Push the stack frame and pass the arguments.
/// For now, arguments must be scalars (so that the caller does not have to know the layout).
///
/// If you do not provie a return place, a dangling zero-sized place will be created
/// If you do not provide a return place, a dangling zero-sized place will be created
/// for your convenience.
fn call_function(
&mut self,

View File

@ -427,7 +427,7 @@ pub struct MiriMachine<'mir, 'tcx> {
/// the emulated program.
profiler: Option<measureme::Profiler>,
/// Used with `profiler` to cache the `StringId`s for event names
/// uesd with `measureme`.
/// used with `measureme`.
string_cache: FxHashMap<String, measureme::StringId>,
/// Cache of `Instance` exported under the given `Symbol` name.
@ -516,7 +516,7 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> {
let pid = process::id();
// We adopt the same naming scheme for the profiler output that rustc uses. In rustc,
// the PID is padded so that the nondeterministic value of the PID does not spread
// nondeterminisim to the allocator. In Miri we are not aiming for such performance
// nondeterminism to the allocator. In Miri we are not aiming for such performance
// control, we just pad for consistency with rustc.
let filename = format!("{crate_name}-{pid:07}");
let path = Path::new(out).join(filename);
@ -1219,7 +1219,8 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
// If we have a borrow tracker, we also have it set up protection so that all reads *and
// writes* during this call are insta-UB.
if ecx.machine.borrow_tracker.is_some() {
if let Either::Left(place) = place.as_mplace_or_local() {
// Have to do `to_op` first because a `Place::Local` doesn't imply the local doesn't have an address.
if let Either::Left(place) = ecx.place_to_op(place)?.as_mplace_or_imm() {
ecx.protect_place(&place)?;
} else {
// Locals that don't have their address taken are as protected as they can ever be.

View File

@ -73,11 +73,11 @@ fn test_config(target: &str, path: &str, mode: Mode, with_dependencies: bool) ->
program.args.push(flag);
}
let bless = env::var_os("RUSTC_BLESS").is_some_and(|v| v !="0");
let bless = env::var_os("RUSTC_BLESS").is_some_and(|v| v != "0");
let skip_ui_checks = env::var_os("MIRI_SKIP_UI_CHECKS").is_some();
let output_conflict_handling = match (bless, skip_ui_checks) {
(false, false) => OutputConflictHandling::Error("./miri bless".into()),
(false, false) => OutputConflictHandling::Error("./miri test --bless".into()),
(true, false) => OutputConflictHandling::Bless,
(false, true) => OutputConflictHandling::Ignore,
(true, true) => panic!("cannot use RUSTC_BLESS and MIRI_SKIP_UI_CHECKS at the same time"),

View File

@ -0,0 +1,29 @@
//@revisions: stack tree
//@compile-flags: -Zmiri-preemption-rate=0
//@[tree]compile-flags: -Zmiri-tree-borrows
use std::thread;
#[derive(Copy, Clone)]
struct SendPtr(*mut i32);
unsafe impl Send for SendPtr {}
fn main() {
let mut mem = 0;
let ptr = SendPtr(&mut mem as *mut _);
let t = thread::spawn(move || {
let ptr = ptr;
// We do a protected 2phase retag (but no write!) in this thread.
fn retag(_x: &mut i32) {} //~[tree]ERROR: Data race detected between (1) Read on thread `main` and (2) Write on thread `<unnamed>`
retag(unsafe { &mut *ptr.0 }); //~[stack]ERROR: Data race detected between (1) Read on thread `main` and (2) Write on thread `<unnamed>`
});
// We do a read in the main thread.
unsafe { ptr.0.read() };
// These two operations do not commute -- if the read happens after the retag, the retagged pointer
// gets frozen! So we want this to be considered UB so that we can still freely move the read around
// in this thread without worrying about reordering with retags in other threads.
t.join().unwrap();
}

View File

@ -0,0 +1,20 @@
error: Undefined Behavior: Data race detected between (1) Read on thread `main` and (2) Write on thread `<unnamed>` at ALLOC. (2) just happened here
--> $DIR/retag_data_race_protected_read.rs:LL:CC
|
LL | retag(unsafe { &mut *ptr.0 });
| ^^^^^^^^^^^ Data race detected between (1) Read on thread `main` and (2) Write on thread `<unnamed>` at ALLOC. (2) just happened here
|
help: and (1) occurred earlier here
--> $DIR/retag_data_race_protected_read.rs:LL:CC
|
LL | unsafe { ptr.0.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 (of the first span):
= note: inside closure at $DIR/retag_data_race_protected_read.rs:LL:CC
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to previous error

View File

@ -0,0 +1,25 @@
error: Undefined Behavior: Data race detected between (1) Read on thread `main` and (2) Write on thread `<unnamed>` at ALLOC. (2) just happened here
--> $DIR/retag_data_race_protected_read.rs:LL:CC
|
LL | fn retag(_x: &mut i32) {}
| ^^ Data race detected between (1) Read on thread `main` and (2) Write on thread `<unnamed>` at ALLOC. (2) just happened here
|
help: and (1) occurred earlier here
--> $DIR/retag_data_race_protected_read.rs:LL:CC
|
LL | unsafe { ptr.0.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 (of the first span):
= note: inside `main::{closure#0}::retag` at $DIR/retag_data_race_protected_read.rs:LL:CC
note: inside closure
--> $DIR/retag_data_race_protected_read.rs:LL:CC
|
LL | ... retag(unsafe { &mut *ptr.0 });
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to previous error

View File

@ -1,5 +1,7 @@
//! Make sure that a retag acts like a write for the data race model.
//@revisions: stack tree
//@compile-flags: -Zmiri-preemption-rate=0
//@[tree]compile-flags: -Zmiri-tree-borrows
#[derive(Copy, Clone)]
struct SendPtr(*mut u8);
@ -15,7 +17,7 @@ fn thread_1(p: SendPtr) {
fn thread_2(p: SendPtr) {
let p = p.0;
unsafe {
*p = 5; //~ ERROR: Data race detected between (1) Write on thread `<unnamed>` and (2) Write on thread `<unnamed>`
*p = 5; //~ ERROR: /Data race detected between \(1\) (Read|Write) on thread `<unnamed>` and \(2\) Write on thread `<unnamed>`/
}
}

View File

@ -0,0 +1,25 @@
error: Undefined Behavior: Data race detected between (1) Read on thread `<unnamed>` and (2) Write on thread `<unnamed>` at ALLOC. (2) just happened here
--> $DIR/retag_data_race_write.rs:LL:CC
|
LL | *p = 5;
| ^^^^^^ Data race detected between (1) Read on thread `<unnamed>` and (2) Write on thread `<unnamed>` at ALLOC. (2) just happened here
|
help: and (1) occurred earlier here
--> $DIR/retag_data_race_write.rs:LL:CC
|
LL | let _r = &mut *p;
| ^^^^^^^
= 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 (of the first span):
= note: inside `thread_2` at $DIR/retag_data_race_write.rs:LL:CC
note: inside closure
--> $DIR/retag_data_race_write.rs:LL:CC
|
LL | let t2 = std::thread::spawn(move || thread_2(p));
| ^^^^^^^^^^^
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to previous error

View File

@ -0,0 +1,30 @@
//@compile-flags: -Zmiri-tree-borrows
#![feature(raw_ref_op)]
#![feature(core_intrinsics)]
#![feature(custom_mir)]
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. Even just reading from that pointer is UB.
Call(x, after_call, myfun(ptr))
}
after_call = {
Return()
}
}
}
fn myfun(ptr: *mut i32) -> i32 {
// This overwrites the return place, which shouldn't be possible through another pointer.
unsafe { ptr.write(0) };
//~^ ERROR: /write access .* forbidden/
13
}

View File

@ -0,0 +1,39 @@
error: Undefined Behavior: write access through <TAG> (root of the allocation) is forbidden
--> $DIR/return_pointer_aliasing2.rs:LL:CC
|
LL | unsafe { ptr.write(0) };
| ^^^^^^^^^^^^ write access through <TAG> (root of the allocation) 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_aliasing2.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 Active
--> $DIR/return_pointer_aliasing2.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 `main`
--> $DIR/return_pointer_aliasing2.rs:LL:CC
|
LL | Call(x, after_call, myfun(ptr))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= 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 previous error

View File

@ -1,23 +1,23 @@
error: Undefined Behavior: Data race detected between (1) Read on thread `<unnamed>` and (2) Write on thread `<unnamed>` at ALLOC. (2) just happened here
--> $DIR/retag-data-race.rs:LL:CC
--> $DIR/retag_data_race_read.rs:LL:CC
|
LL | *p = 5;
| ^^^^^^ Data race detected between (1) Read on thread `<unnamed>` and (2) Write on thread `<unnamed>` at ALLOC. (2) just happened here
LL | *p = 5;
| ^^^^^^ Data race detected between (1) Read on thread `<unnamed>` and (2) Write on thread `<unnamed>` at ALLOC. (2) just happened here
|
help: and (1) occurred earlier here
--> $DIR/retag-data-race.rs:LL:CC
--> $DIR/retag_data_race_read.rs:LL:CC
|
LL | let _r = &*p;
| ^^^
LL | let _r = &*p;
| ^^^
= 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 (of the first span):
= note: inside `thread_2` at $DIR/retag-data-race.rs:LL:CC
= note: inside `thread_2` at $DIR/retag_data_race_read.rs:LL:CC
note: inside closure
--> $DIR/retag-data-race.rs:LL:CC
--> $DIR/retag_data_race_read.rs:LL:CC
|
LL | let t2 = std::thread::spawn(move || unsafe { thread_2(p) });
| ^^^^^^^^^^^
LL | let t2 = std::thread::spawn(move || thread_2(p));
| ^^^^^^^^^^^
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

View File

@ -0,0 +1,26 @@
error: Undefined Behavior: reborrow through <TAG> (root of the allocation) is forbidden
--> RUSTLIB/std/src/rt.rs:LL:CC
|
LL | panic::catch_unwind(move || unsafe { init(argc, argv, sigpipe) }).map_err(rt_abort)?;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ reborrow through <TAG> (root of the allocation) 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 reborrow (acting as a 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
--> RUSTLIB/std/src/rt.rs:LL:CC
|
LL | panic::catch_unwind(move || unsafe { init(argc, argv, sigpipe) }).map_err(rt_abort)?;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: the protected tag <TAG> was created here, in the initial state Active
--> RUSTLIB/std/src/panic.rs:LL:CC
|
LL | pub fn catch_unwind<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Result<R> {
| ^
= note: BACKTRACE (of the first span):
= note: inside `std::rt::lang_start_internal` at RUSTLIB/std/src/rt.rs:LL:CC
= note: inside `std::rt::lang_start::<()>` at RUSTLIB/std/src/rt.rs:LL:CC
error: aborting due to previous error

View File

@ -1,42 +0,0 @@
//! Race-condition-like interaction between a read and a reborrow.
//! Even though no write or fake write occurs, reads have an effect on protected
//! Reserved. This is a protected-retag/read data race, but is not *detected* as
//! a data race violation because reborrows are not writes.
//!
//! This test is sensitive to the exact schedule so we disable preemption.
//@compile-flags: -Zmiri-tree-borrows -Zmiri-preemption-rate=0
use std::ptr::addr_of_mut;
use std::thread;
#[derive(Copy, Clone)]
struct SendPtr(*mut u8);
unsafe impl Send for SendPtr {}
// First thread is just a reborrow, but for an instant `x` is
// protected and thus vulnerable to foreign reads.
fn thread_1(x: &mut u8) -> SendPtr {
thread::yield_now(); // make the other thread go first
SendPtr(x as *mut u8)
}
// Second thread simply performs a read.
fn thread_2(x: &u8) {
let _val = *x;
}
fn main() {
let mut x = 0u8;
let x_1 = unsafe { &mut *addr_of_mut!(x) };
let xg = unsafe { &*addr_of_mut!(x) };
// The two threads are executed in parallel on aliasing pointers.
// UB occurs if the read of thread_2 occurs while the protector of thread_1
// is in place.
let hf = thread::spawn(move || thread_1(x_1));
let hg = thread::spawn(move || thread_2(xg));
let SendPtr(p) = hf.join().unwrap();
let () = hg.join().unwrap();
unsafe { *p = 1 }; //~ ERROR: /write access through .* is forbidden/
}

View File

@ -1,32 +0,0 @@
error: Undefined Behavior: write access through <TAG> is forbidden
--> $DIR/fragile-data-race.rs:LL:CC
|
LL | unsafe { *p = 1 };
| ^^^^^^ write access through <TAG> 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> is a child of the conflicting tag <TAG>
= help: the conflicting tag <TAG> has state Frozen which forbids this child write access
help: the accessed tag <TAG> was created here
--> $DIR/fragile-data-race.rs:LL:CC
|
LL | fn thread_1(x: &mut u8) -> SendPtr {
| ^
help: the conflicting tag <TAG> was created here, in the initial state Reserved
--> RUSTLIB/std/src/panic.rs:LL:CC
|
LL | pub fn catch_unwind<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Result<R> {
| ^
help: the conflicting tag <TAG> later transitioned to Frozen due to a reborrow (acting as a foreign read access) at offsets [0x0..0x1]
--> RUSTLIB/core/src/ptr/mod.rs:LL:CC
|
LL | crate::intrinsics::read_via_copy(src)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= help: this transition corresponds to a loss of write permissions
= note: BACKTRACE (of the first span):
= note: inside `main` at $DIR/fragile-data-race.rs:LL:CC
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to previous error

View File

@ -3,8 +3,8 @@
// Check how a Reserved with interior mutability
// responds to a Foreign Write under a Protector
#[path = "../../../utils/mod.rs"]
#[macro_use]
mod utils;
use utils::macros::*;
use std::cell::UnsafeCell;

View File

@ -1,8 +1,8 @@
//@compile-flags: -Zmiri-tree-borrows -Zmiri-tag-gc=0
#[path = "../../../utils/mod.rs"]
#[macro_use]
mod utils;
use utils::macros::*;
// Check how a Reserved without interior mutability responds to a Foreign
// Write when under a protector

View File

@ -1,28 +0,0 @@
//! Make sure that a retag acts like a read for the data race model.
//! This is a retag/write race condition.
//!
//! This test is sensitive to the exact schedule so we disable preemption.
//@compile-flags: -Zmiri-tree-borrows -Zmiri-preemption-rate=0
#[derive(Copy, Clone)]
struct SendPtr(*mut u8);
unsafe impl Send for SendPtr {}
unsafe fn thread_1(SendPtr(p): SendPtr) {
let _r = &*p;
}
unsafe fn thread_2(SendPtr(p): SendPtr) {
*p = 5; //~ ERROR: Data race detected between (1) Read on thread `<unnamed>` and (2) Write on thread `<unnamed>`
}
fn main() {
let mut x = 0;
let p = std::ptr::addr_of_mut!(x);
let p = SendPtr(p);
let t1 = std::thread::spawn(move || unsafe { thread_1(p) });
let t2 = std::thread::spawn(move || unsafe { thread_2(p) });
let _ = t1.join();
let _ = t2.join();
}

View File

@ -0,0 +1,24 @@
/// This tests that when a field sits at offset 0 in a 4-aligned struct, accessing the field
/// requires alignment 4 even if the field type has lower alignment requirements.
#[repr(C)]
pub struct S {
x: u8,
y: u32,
}
unsafe fn foo(x: *const S) -> u8 {
unsafe { (*x).x } //~ERROR: accessing memory with alignment 1, but alignment 4 is required
}
fn main() {
unsafe {
let mem = [0u64; 16];
let odd_ptr = std::ptr::addr_of!(mem).cast::<u8>().add(1);
// `odd_ptr` is now not aligned enough for `S`.
// If accessing field `x` can exploit that it is at offset 0
// in a 4-aligned struct, that field access requires alignment 4,
// thus making this UB.
foo(odd_ptr.cast());
}
}

View File

@ -0,0 +1,20 @@
error: Undefined Behavior: accessing memory with alignment ALIGN, but alignment ALIGN is required
--> $DIR/field_requires_parent_struct_alignment.rs:LL:CC
|
LL | unsafe { (*x).x }
| ^^^^^^ accessing memory with alignment ALIGN, but alignment ALIGN is required
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE:
= note: inside `foo` at $DIR/field_requires_parent_struct_alignment.rs:LL:CC
note: inside `main`
--> $DIR/field_requires_parent_struct_alignment.rs:LL:CC
|
LL | foo(odd_ptr.cast());
| ^^^^^^^^^^^^^^^^^^^
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to previous error

View File

@ -5,12 +5,15 @@
#![feature(io_error_uncategorized)]
use std::convert::TryInto;
use std::ffi::{c_char, CStr, CString};
use std::ffi::CString;
use std::fs::{canonicalize, remove_dir_all, remove_file, File};
use std::io::{Error, ErrorKind, Write};
use std::os::unix::ffi::OsStrExt;
use std::path::PathBuf;
#[path = "../../utils/mod.rs"]
mod utils;
fn main() {
test_dup_stdout_stderr();
test_canonicalize_too_long();
@ -22,31 +25,9 @@ fn main() {
test_o_tmpfile_flag();
}
fn tmp() -> PathBuf {
let path = std::env::var("MIRI_TEMP")
.unwrap_or_else(|_| std::env::temp_dir().into_os_string().into_string().unwrap());
// These are host paths. We need to convert them to the target.
let path = CString::new(path).unwrap();
let mut out = Vec::with_capacity(1024);
unsafe {
extern "Rust" {
fn miri_host_to_target_path(
path: *const c_char,
out: *mut c_char,
out_size: usize,
) -> usize;
}
let ret = miri_host_to_target_path(path.as_ptr(), out.as_mut_ptr(), out.capacity());
assert_eq!(ret, 0);
let out = CStr::from_ptr(out.as_ptr()).to_str().unwrap();
PathBuf::from(out)
}
}
/// Prepare: compute filename and make sure the file does not exist.
fn prepare(filename: &str) -> PathBuf {
let path = tmp().join(filename);
let path = utils::tmp().join(filename);
// Clean the paths for robustness.
remove_file(&path).ok();
path
@ -55,7 +36,7 @@ fn prepare(filename: &str) -> PathBuf {
/// Prepare directory: compute directory name and make sure it does not exist.
#[allow(unused)]
fn prepare_dir(dirname: &str) -> PathBuf {
let path = tmp().join(&dirname);
let path = utils::tmp().join(&dirname);
// Clean the directory for robustness.
remove_dir_all(&path).ok();
path

View File

@ -6,29 +6,8 @@ use std::fs::{remove_file, File};
use std::os::unix::io::AsRawFd;
use std::path::PathBuf;
fn tmp() -> PathBuf {
use std::ffi::{c_char, CStr, CString};
let path = std::env::var("MIRI_TEMP")
.unwrap_or_else(|_| std::env::temp_dir().into_os_string().into_string().unwrap());
// These are host paths. We need to convert them to the target.
let path = CString::new(path).unwrap();
let mut out = Vec::with_capacity(1024);
unsafe {
extern "Rust" {
fn miri_host_to_target_path(
path: *const c_char,
out: *mut c_char,
out_size: usize,
) -> usize;
}
let ret = miri_host_to_target_path(path.as_ptr(), out.as_mut_ptr(), out.capacity());
assert_eq!(ret, 0);
let out = CStr::from_ptr(out.as_ptr()).to_str().unwrap();
PathBuf::from(out)
}
}
#[path = "../../utils/mod.rs"]
mod utils;
/// Test allocating variant of `realpath`.
fn test_posix_realpath_alloc() {
@ -38,7 +17,7 @@ fn test_posix_realpath_alloc() {
use std::os::unix::ffi::OsStringExt;
let buf;
let path = tmp().join("miri_test_libc_posix_realpath_alloc");
let path = utils::tmp().join("miri_test_libc_posix_realpath_alloc");
let c_path = CString::new(path.as_os_str().as_bytes()).expect("CString::new failed");
// Cleanup before test.
@ -63,7 +42,7 @@ fn test_posix_realpath_noalloc() {
use std::ffi::{CStr, CString};
use std::os::unix::ffi::OsStrExt;
let path = tmp().join("miri_test_libc_posix_realpath_noalloc");
let path = utils::tmp().join("miri_test_libc_posix_realpath_noalloc");
let c_path = CString::new(path.as_os_str().as_bytes()).expect("CString::new failed");
let mut v = vec![0; libc::PATH_MAX as usize];
@ -103,7 +82,7 @@ fn test_posix_realpath_errors() {
fn test_posix_fadvise() {
use std::io::Write;
let path = tmp().join("miri_test_libc_posix_fadvise.txt");
let path = utils::tmp().join("miri_test_libc_posix_fadvise.txt");
// Cleanup before test
remove_file(&path).ok();
@ -130,7 +109,7 @@ fn test_posix_fadvise() {
fn test_sync_file_range() {
use std::io::Write;
let path = tmp().join("miri_test_libc_sync_file_range.txt");
let path = utils::tmp().join("miri_test_libc_sync_file_range.txt");
// Cleanup before test.
remove_file(&path).ok();
@ -243,7 +222,7 @@ fn test_isatty() {
libc::isatty(libc::STDERR_FILENO);
// But when we open a file, it is definitely not a TTY.
let path = tmp().join("notatty.txt");
let path = utils::tmp().join("notatty.txt");
// Cleanup before test.
remove_file(&path).ok();
let file = File::create(&path).unwrap();

View File

@ -2,17 +2,21 @@
use std::intrinsics::mir::*;
#[repr(packed)]
struct S { field: [u32; 2] }
struct S {
field: [u32; 2],
}
#[custom_mir(dialect = "runtime", phase = "optimized")]
fn test() { mir! {
let s: S;
{
// Store a repeat expression directly into a field of a packed struct.
s.field = [0; 2];
Return()
fn test() {
mir! {
let s: S;
{
// Store a repeat expression directly into a field of a packed struct.
s.field = [0; 2];
Return()
}
}
} }
}
fn main() {
// Run this a bunch of time to make sure it doesn't pass by chance.

View File

@ -0,0 +1,23 @@
#![allow(unused)]
#[repr(u16)]
enum DeviceKind {
Nil = 0,
}
#[repr(C, packed)]
struct DeviceInfo {
endianness: u8,
device_kind: DeviceKind,
}
fn main() {
// The layout of `Option<(DeviceInfo, u64)>` is funny: it uses the
// `DeviceKind` enum as niche, so that is offset 1, but the niche type is u16!
// So despite the type having alignment 8 and the field type alignment 2,
// the actual alignment is 1.
let x = None::<(DeviceInfo, u8)>;
let y = None::<(DeviceInfo, u16)>;
let z = None::<(DeviceInfo, u64)>;
format!("{} {} {}", x.is_some(), y.is_some(), y.is_some());
}

View File

@ -1,18 +0,0 @@
#[repr(u16)]
#[allow(dead_code)]
enum DeviceKind {
Nil = 0,
}
#[repr(packed)]
#[allow(dead_code)]
struct DeviceInfo {
endianness: u8,
device_kind: DeviceKind,
}
fn main() {
let _x = None::<(DeviceInfo, u8)>;
let _y = None::<(DeviceInfo, u16)>;
let _z = None::<(DeviceInfo, u64)>;
}

View File

@ -20,6 +20,15 @@ fn basic_raw() {
assert_eq!(*x, 23);
}
fn assign_overlapping() {
// Test an assignment where LHS and RHS alias.
// In Mir, that's UB (see `fail/overlapping_assignment.rs`), but in surface Rust this is allowed.
let mut mem = [0u32; 4];
let ptr = &mut mem as *mut [u32; 4];
unsafe { *ptr = *ptr };
}
fn main() {
basic_raw();
assign_overlapping();
}

View File

@ -5,7 +5,7 @@
#![feature(io_error_uncategorized)]
use std::collections::HashMap;
use std::ffi::{c_char, OsString};
use std::ffi::OsString;
use std::fs::{
canonicalize, create_dir, read_dir, read_link, remove_dir, remove_dir_all, remove_file, rename,
File, OpenOptions,
@ -13,6 +13,9 @@ use std::fs::{
use std::io::{Error, ErrorKind, IsTerminal, Read, Result, Seek, SeekFrom, Write};
use std::path::{Path, PathBuf};
#[path = "../../utils/mod.rs"]
mod utils;
fn main() {
test_path_conversion();
test_file();
@ -30,37 +33,9 @@ fn main() {
test_from_raw_os_error();
}
fn host_to_target_path(path: String) -> PathBuf {
use std::ffi::{CStr, CString};
let path = CString::new(path).unwrap();
let mut out = Vec::with_capacity(1024);
unsafe {
extern "Rust" {
fn miri_host_to_target_path(
path: *const c_char,
out: *mut c_char,
out_size: usize,
) -> usize;
}
let ret = miri_host_to_target_path(path.as_ptr(), out.as_mut_ptr(), out.capacity());
assert_eq!(ret, 0);
let out = CStr::from_ptr(out.as_ptr()).to_str().unwrap();
PathBuf::from(out)
}
}
fn tmp() -> PathBuf {
let path = std::env::var("MIRI_TEMP")
.unwrap_or_else(|_| std::env::temp_dir().into_os_string().into_string().unwrap());
// These are host paths. We need to convert them to the target.
host_to_target_path(path)
}
/// Prepare: compute filename and make sure the file does not exist.
fn prepare(filename: &str) -> PathBuf {
let path = tmp().join(filename);
let path = utils::tmp().join(filename);
// Clean the paths for robustness.
remove_file(&path).ok();
path
@ -68,7 +43,7 @@ fn prepare(filename: &str) -> PathBuf {
/// Prepare directory: compute directory name and make sure it does not exist.
fn prepare_dir(dirname: &str) -> PathBuf {
let path = tmp().join(&dirname);
let path = utils::tmp().join(&dirname);
// Clean the directory for robustness.
remove_dir_all(&path).ok();
path
@ -83,7 +58,7 @@ fn prepare_with_content(filename: &str, content: &[u8]) -> PathBuf {
}
fn test_path_conversion() {
let tmp = tmp();
let tmp = utils::tmp();
assert!(tmp.is_absolute(), "{:?} is not absolute", tmp);
assert!(tmp.is_dir(), "{:?} is not a directory", tmp);
}

View File

@ -10,7 +10,7 @@ fn main() {
mut_raw_mut();
partially_invalidate_mut();
drop_after_sharing();
direct_mut_to_const_raw();
// direct_mut_to_const_raw();
two_raw();
shr_and_raw();
disjoint_mutable_subborrows();
@ -19,6 +19,7 @@ fn main() {
mut_below_shr();
wide_raw_ptr_in_tuple();
not_unpin_not_protected();
write_does_not_invalidate_all_aliases();
}
// Make sure that reading from an `&mut` does, like reborrowing to `&`,
@ -110,14 +111,13 @@ fn drop_after_sharing() {
}
// Make sure that coercing &mut T to *const T produces a writeable pointer.
fn direct_mut_to_const_raw() {
// TODO: This is currently disabled, waiting on a decision on <https://github.com/rust-lang/rust/issues/56604>
/*let x = &mut 0;
// TODO: This is currently disabled, waiting on a decision on <https://github.com/rust-lang/rust/issues/56604>
/*fn direct_mut_to_const_raw() {
let x = &mut 0;
let y: *const i32 = x;
unsafe { *(y as *mut i32) = 1; }
assert_eq!(*x, 1);
*/
}
}*/
// Make sure that we can create two raw pointers from a mutable reference and use them both.
fn two_raw() {
@ -238,3 +238,28 @@ fn not_unpin_not_protected() {
drop(unsafe { Box::from_raw(raw) });
});
}
fn write_does_not_invalidate_all_aliases() {
mod other {
/// Some private memory to store stuff in.
static mut S: *mut i32 = 0 as *mut i32;
pub fn lib1(x: &&mut i32) {
unsafe {
S = (x as *const &mut i32).cast::<*mut i32>().read();
}
}
pub fn lib2() {
unsafe {
*S = 1337;
}
}
}
let x = &mut 0;
other::lib1(&x);
*x = 42; // a write to x -- invalidates other pointers?
other::lib2();
assert_eq!(*x, 1337); // oops, the value changed! I guess not all pointers were invalidated
}

View File

@ -0,0 +1,25 @@
//@revisions: stack tree
//@[tree]compile-flags: -Zmiri-tree-borrows
// Create zero-sized references to vtables and function data.
// Just make sure nothing explodes.
use std::{mem, ptr};
fn check_ref(x: &()) {
let _ptr = ptr::addr_of!(*x);
}
fn main() {
check_ref({
// Create reference to a function.
let fnptr: fn(&()) = check_ref;
unsafe { mem::transmute(fnptr) }
});
check_ref({
// Create reference to a vtable.
let wideptr: &dyn Send = &0;
let fields: (&i32, &()) = unsafe { mem::transmute(wideptr) };
fields.1
})
}

View File

@ -1,7 +1,7 @@
//@compile-flags: -Zmiri-tree-borrows -Zmiri-tag-gc=0
#[path = "../../utils/mod.rs"]
#[macro_use]
mod utils;
use utils::macros::*;
use std::cell::UnsafeCell;

View File

@ -3,8 +3,8 @@
// Check that a protector goes back to normal behavior when the function
// returns.
#[path = "../../utils/mod.rs"]
#[macro_use]
mod utils;
use utils::macros::*;
fn main() {
unsafe {

View File

@ -1,8 +1,8 @@
//@compile-flags: -Zmiri-tree-borrows -Zmiri-tag-gc=0
#[path = "../../utils/mod.rs"]
#[macro_use]
mod utils;
use utils::macros::*;
// Check the formatting of the trees.
fn main() {

View File

@ -1,8 +1,8 @@
//@compile-flags: -Zmiri-tree-borrows -Zmiri-tag-gc=0
#[path = "../../utils/mod.rs"]
#[macro_use]
mod utils;
use utils::macros::*;
// To check that a reborrow is counted as a Read access, we use a reborrow
// with no additional Read to Freeze an Active pointer.

View File

@ -1,9 +1,8 @@
//@compile-flags: -Zmiri-tree-borrows -Zmiri-tag-gc=0
#[path = "../../utils/mod.rs"]
#[macro_use]
mod utils;
use utils::macros::*;
use utils::miri_extern::miri_write_to_stderr;
use std::cell::UnsafeCell;
@ -28,8 +27,8 @@ fn main() {
}
unsafe fn print(msg: &str) {
miri_write_to_stderr(msg.as_bytes());
miri_write_to_stderr("\n".as_bytes());
utils::miri_write_to_stderr(msg.as_bytes());
utils::miri_write_to_stderr("\n".as_bytes());
}
unsafe fn read_second<T>(x: &mut T, y: *mut u8) {

View File

@ -10,6 +10,8 @@ fn main() {
aliasing_read_only_mutable_refs();
string_as_mut_ptr();
two_mut_protected_same_alloc();
direct_mut_to_const_raw();
local_addr_of_mut();
// Stacked Borrows tests
read_does_not_invalidate1();
@ -19,7 +21,6 @@ fn main() {
mut_raw_mut();
partially_invalidate_mut();
drop_after_sharing();
direct_mut_to_const_raw();
two_raw();
shr_and_raw();
disjoint_mutable_subborrows();
@ -28,6 +29,18 @@ fn main() {
mut_below_shr();
wide_raw_ptr_in_tuple();
not_unpin_not_protected();
write_does_not_invalidate_all_aliases();
}
#[allow(unused_assignments)]
fn local_addr_of_mut() {
let mut local = 0;
let ptr = ptr::addr_of_mut!(local);
// In SB, `local` and `*ptr` would have different tags, but in TB they have the same tag.
local = 1;
unsafe { *ptr = 2 };
local = 3;
unsafe { *ptr = 4 };
}
// Tree Borrows has no issue with several mutable references existing
@ -172,12 +185,12 @@ fn drop_after_sharing() {
// Make sure that coercing &mut T to *const T produces a writeable pointer.
fn direct_mut_to_const_raw() {
// TODO: This is currently disabled, waiting on a decision on <https://github.com/rust-lang/rust/issues/56604>
/*let x = &mut 0;
let x = &mut 0;
let y: *const i32 = x;
unsafe { *(y as *mut i32) = 1; }
unsafe {
*(y as *mut i32) = 1;
}
assert_eq!(*x, 1);
*/
}
// Make sure that we can create two raw pointers from a mutable reference and use them both.
@ -298,3 +311,31 @@ fn not_unpin_not_protected() {
drop(unsafe { Box::from_raw(raw) });
});
}
fn write_does_not_invalidate_all_aliases() {
// In TB there are other ways to do that (`addr_of!(*x)` has the same tag as `x`),
// but let's still make sure this SB test keeps working.
mod other {
/// Some private memory to store stuff in.
static mut S: *mut i32 = 0 as *mut i32;
pub fn lib1(x: &&mut i32) {
unsafe {
S = (x as *const &mut i32).cast::<*mut i32>().read();
}
}
pub fn lib2() {
unsafe {
*S = 1337;
}
}
}
let x = &mut 0;
other::lib1(&x);
*x = 42; // a write to x -- invalidates other pointers?
other::lib2();
assert_eq!(*x, 1337); // oops, the value changed! I guess not all pointers were invalidated
}

View File

@ -5,8 +5,8 @@
#![feature(ptr_internals)]
#[path = "../../utils/mod.rs"]
#[macro_use]
mod utils;
use utils::macros::*;
use core::ptr::Unique;

View File

@ -5,8 +5,8 @@
#![feature(vec_into_raw_parts)]
#[path = "../../utils/mod.rs"]
#[macro_use]
mod utils;
use utils::macros::*;
// Check general handling of `Unique`:
// there is no *explicit* `Unique` being used here, but there is one

View File

@ -0,0 +1,29 @@
use std::ffi::OsString;
use std::path::PathBuf;
use super::miri_extern;
pub fn host_to_target_path(path: OsString) -> PathBuf {
use std::ffi::{CStr, CString};
// Once into_os_str_bytes is stable we can use it here.
// (Unstable features would need feature flags in each test...)
let path = CString::new(path.into_string().unwrap()).unwrap();
let mut out = Vec::with_capacity(1024);
unsafe {
let ret =
miri_extern::miri_host_to_target_path(path.as_ptr(), out.as_mut_ptr(), out.capacity());
assert_eq!(ret, 0);
// Here we panic if it's not UTF-8... but that is hard to avoid with OsStr APIs.
let out = CStr::from_ptr(out.as_ptr()).to_str().unwrap();
PathBuf::from(out)
}
}
pub fn tmp() -> PathBuf {
let path =
std::env::var_os("MIRI_TEMP").unwrap_or_else(|| std::env::temp_dir().into_os_string());
// These are host paths. We need to convert them to the target.
host_to_target_path(path)
}

View File

@ -9,7 +9,7 @@
/// The id obtained can be passed directly to `print_state!`.
macro_rules! alloc_id {
($ptr:expr) => {
crate::utils::miri_extern::miri_get_alloc_id($ptr as *const u8 as *const ())
$crate::utils::miri_get_alloc_id($ptr as *const u8 as *const ())
};
}
@ -22,10 +22,10 @@ macro_rules! alloc_id {
/// tags that have not been given a name. Defaults to `false`.
macro_rules! print_state {
($alloc_id:expr) => {
crate::utils::macros::print_state!($alloc_id, false);
print_state!($alloc_id, false);
};
($alloc_id:expr, $show:expr) => {
crate::utils::miri_extern::miri_print_borrow_state($alloc_id, $show);
$crate::utils::miri_print_borrow_state($alloc_id, $show);
};
}
@ -42,20 +42,16 @@ macro_rules! print_state {
/// `stringify!($ptr)` the name of `ptr` in the source code.
macro_rules! name {
($ptr:expr, $name:expr) => {
crate::utils::macros::name!($ptr => 0, $name);
name!($ptr => 0, $name);
};
($ptr:expr) => {
crate::utils::macros::name!($ptr => 0, stringify!($ptr));
name!($ptr => 0, stringify!($ptr));
};
($ptr:expr => $nth_parent:expr) => {
crate::utils::macros::name!($ptr => $nth_parent, stringify!($ptr));
name!($ptr => $nth_parent, stringify!($ptr));
};
($ptr:expr => $nth_parent:expr, $name:expr) => {
let name = $name.as_bytes();
crate::utils::miri_extern::miri_pointer_name($ptr as *const u8 as *const (), $nth_parent, name);
$crate::utils::miri_pointer_name($ptr as *const u8 as *const (), $nth_parent, name);
};
}
pub(crate) use alloc_id;
pub(crate) use name;
pub(crate) use print_state;

View File

@ -1,5 +1,3 @@
#![allow(dead_code)]
#[repr(C)]
/// Layout of the return value of `miri_resolve_frame`,
/// with fields in the exact same order.

View File

@ -1,2 +1,10 @@
pub mod macros;
pub mod miri_extern;
#![allow(dead_code)]
#[macro_use]
mod macros;
mod fs;
mod miri_extern;
pub use fs::*;
pub use miri_extern::*;