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 - name: clippy
run: ./miri clippy -- -D warnings run: ./miri clippy -- -D warnings
- name: rustdoc - 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 # 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 # 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 ### UI testing
We use ui-testing in Miri, meaning we generate `.stderr` and `.stdout` files for the output 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. 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 Note that when you also use `MIRIFLAGS` to change optimizations and similar, the ui output

View File

@ -56,7 +56,7 @@ fn main() {
return; 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. // arguments alone. `phase_cargo_rustdoc` sets this environment variable to let us disambiguate.
if env::var_os("MIRI_CALLED_FROM_RUSTDOC").is_some() { if env::var_os("MIRI_CALLED_FROM_RUSTDOC").is_some() {
// ...however, we then also see this variable when rustdoc invokes us as the testrunner! // ...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); let target = target.as_ref().unwrap_or(host);
// We always setup. // 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. // 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 // 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. // Forward all further arguments (not consumed by `ArgSplitFlagValue`) to cargo.
cmd.args(args); 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, // 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 // 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`.) // 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`. // `.rmeta`.
// We also need to remove `--error-format` as cargo specifies that to be JSON, // 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` // 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 mut args = info.args.into_iter();
let error_format_flag = "--error-format"; let error_format_flag = "--error-format";
let json_flag = "--json"; let json_flag = "--json";
@ -538,8 +540,7 @@ pub fn phase_runner(mut binary_args: impl Iterator<Item = String>, phase: Runner
} }
// Respect `MIRIFLAGS`. // Respect `MIRIFLAGS`.
if let Ok(a) = env::var("MIRIFLAGS") { if let Ok(a) = env::var("MIRIFLAGS") {
// This code is taken from `RUSTFLAGS` handling in cargo. let args = flagsplit(&a);
let args = a.split(' ').map(str::trim).filter(|s| !s.is_empty()).map(str::to_string);
cmd.args(args); 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 /// 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 /// `MIRI_SYSROOT`. Skipped if `MIRI_SYSROOT` is already set, in which case we expect the user has
/// done all this already. /// 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 only_setup = matches!(subcommand, MiriCommand::Setup);
let ask_user = !only_setup; let ask_user = !only_setup;
let print_sysroot = only_setup && has_arg_flag("--print-sysroot"); // whether we just print the sysroot path 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() { if !only_setup {
// Skip setup step if MIRI_SYSROOT is explicitly set, *unless* we are `cargo miri setup`. if let Some(sysroot) = std::env::var_os("MIRI_SYSROOT") {
return; // 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. // 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("RUSTC", &cargo_miri_path);
} }
command.env("MIRI_CALLED_FROM_SETUP", "1"); 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 // 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/1421,
// https://github.com/rust-lang/miri/issues/2429). Looks like setting // 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"); command.arg("-v");
} }
} else { } else {
// Supress output. // Suppress output.
command.stdout(process::Stdio::null()); command.stdout(process::Stdio::null());
command.stderr(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 // the user might have set, which is consistent with normal `cargo build` that does
// not apply `RUSTFLAGS` to the sysroot either. // not apply `RUSTFLAGS` to the sysroot either.
let rustflags = &["-Cdebug-assertions=off", "-Coverflow-checks=on"]; 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. // Do the build.
if print_sysroot { 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. // Print just the sysroot and nothing else to stdout; this way we do not need any escaping.
println!("{}", sysroot_dir.display()); println!("{}", sysroot_dir.display());
} }
sysroot_dir
} }

View File

@ -1,7 +1,5 @@
use std::collections::HashMap;
use std::env; use std::env;
use std::ffi::OsString; use std::ffi::OsString;
use std::fmt::Write as _;
use std::fs::File; use std::fs::File;
use std::io::{self, BufWriter, Read, Write}; use std::io::{self, BufWriter, Read, Write};
use std::ops::Not; use std::ops::Not;
@ -114,6 +112,11 @@ pub fn cargo() -> Command {
Command::new(env::var_os("CARGO").unwrap_or_else(|| OsString::from("cargo"))) 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 /// 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. /// described by the `Command`. Then exit this process with the exit code of the new process.
pub fn exec(mut cmd: Command) -> ! { pub fn exec(mut cmd: Command) -> ! {
@ -242,46 +245,10 @@ pub fn local_crates(metadata: &Metadata) -> String {
local_crates 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. /// Debug-print a command that is going to be run.
pub fn debug_cmd(prefix: &str, verbose: usize, cmd: &Command) { pub fn debug_cmd(prefix: &str, verbose: usize, cmd: &Command) {
if verbose == 0 { if verbose == 0 {
return; return;
} }
// We only do a single `eprintln!` call to minimize concurrency interactions. eprintln!("{prefix} running command: {cmd:?}");
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}");
} }

View File

@ -1,359 +1,6 @@
#!/bin/bash #!/bin/bash
set -e set -e
USAGE=$(cat <<"EOF" # Instead of doing just `cargo run --manifest-path .. $@`, we invoke miri-script binary directly. Invoking `cargo run` goes through
COMMANDS # rustup (that sets it's own environmental variables), which is undesirable.
cargo build -q --manifest-path "$(dirname "$0")"/miri-script/Cargo.toml
./miri install <flags>: "$(dirname "$0")"/miri-script/target/debug/miri-script "$@"
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

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_data_structures::fx::FxHashSet;
use rustc_middle::mir::{Mutability, RetagKind}; use rustc_middle::mir::{Mutability, RetagKind};
use rustc_middle::ty::{ use rustc_middle::ty::{self, layout::HasParamEnv, Ty};
self,
layout::HasParamEnv,
Ty,
};
use rustc_target::abi::{Abi, Align, Size}; use rustc_target::abi::{Abi, Align, Size};
use crate::borrow_tracker::{ 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> { 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 /// Returns the provenance that should be used henceforth.
/// happened.
fn sb_reborrow( fn sb_reborrow(
&mut self, &mut self,
place: &MPlaceTy<'tcx, Provenance>, place: &MPlaceTy<'tcx, Provenance>,
@ -617,7 +612,7 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
new_perm: NewPermission, new_perm: NewPermission,
new_tag: BorTag, new_tag: BorTag,
retag_info: RetagInfo, // diagnostics info about this retag retag_info: RetagInfo, // diagnostics info about this retag
) -> InterpResult<'tcx, Option<AllocId>> { ) -> InterpResult<'tcx, Option<Provenance>> {
let this = self.eval_context_mut(); let this = self.eval_context_mut();
// Ensure we bail out if the pointer goes out-of-bounds (see miri#1050). // 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)?; 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. // 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) { 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)))?; 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)?; 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. /// 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(); let new_tag = this.machine.borrow_tracker.as_mut().unwrap().get_mut().new_ptr();
// Reborrow. // 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. // Adjust pointer.
let new_place = place.map_provenance(|p| { let new_place = place.map_provenance(|_| new_prov);
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
}
}
})
});
// Return new pointer. // Return new pointer.
Ok(ImmTy::from_immediate(new_place.to_ref(this), val.layout)) 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 { pub(super) enum TransitionError {
/// This access is not allowed because some parent tag has insufficient permissions. /// 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 /// 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 crate::borrow_tracker::{AccessKind, GlobalStateInner, ProtectorKind, RetagFields};
use rustc_middle::{ use rustc_middle::{
mir::{Mutability, RetagKind}, mir::{Mutability, RetagKind},
ty::{ ty::{self, layout::HasParamEnv, Ty},
self,
layout::HasParamEnv,
Ty,
},
}; };
use rustc_span::def_id::DefId; 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_freeze = pointee.is_freeze(*cx.tcx, cx.param_env());
let ty_is_unpin = pointee.is_unpin(*cx.tcx, cx.param_env()); let ty_is_unpin = pointee.is_unpin(*cx.tcx, cx.param_env());
let initial_state = match mutability { 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(), Mutability::Not if ty_is_freeze => Permission::new_frozen(),
// Raw pointers never enter this function so they are not handled. // Raw pointers never enter this function so they are not handled.
// However raw pointers are not the only pointers that take the parent // 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()); let ty_is_freeze = ty.is_freeze(*cx.tcx, cx.param_env());
Self { Self {
zero_size, 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), 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> { trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'mir, 'tcx> {
/// Returns the `AllocId` the reborrow was done in, if there is some actual /// Returns the provenance that should be used henceforth.
/// 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 }`).
fn tb_reborrow( fn tb_reborrow(
&mut self, &mut self,
place: &MPlaceTy<'tcx, Provenance>, // parent tag extracted from here place: &MPlaceTy<'tcx, Provenance>, // parent tag extracted from here
ptr_size: Size, ptr_size: Size,
new_perm: NewPermission, new_perm: NewPermission,
new_tag: BorTag, new_tag: BorTag,
) -> InterpResult<'tcx, Option<(AllocId, BorTag)>> { ) -> InterpResult<'tcx, Option<Provenance>> {
let this = self.eval_context_mut(); let this = self.eval_context_mut();
// Ensure we bail out if the pointer goes out-of-bounds (see miri#1050). // 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. // It is crucial that this gets called on all code paths, to ensure we track tag creation.
let log_creation = |this: &MiriInterpCx<'mir, 'tcx>, 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. // 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. // After all, the pointer may be lazily initialized outside this initial range.
data data
}, }
Err(_) => { Err(_) => {
assert_eq!(ptr_size, Size::ZERO); // we did the deref check above, size has to be 0 here 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 // 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, place.layout.ty,
); );
log_creation(this, None)?; 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)))?; log_creation(this, Some((alloc_id, base_offset, parent_prov)))?;
let orig_tag = match 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, ProvenanceExtra::Concrete(tag) => tag,
}; };
@ -254,31 +248,54 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
.insert(new_tag, protect); .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 span = this.machine.current_span();
let alloc_extra = this.get_alloc_extra(alloc_id)?; let alloc_extra = this.get_alloc_extra(alloc_id)?;
let range = alloc_range(base_offset, ptr_size); let range = alloc_range(base_offset, ptr_size);
let mut tree_borrows = alloc_extra.borrow_tracker_tb().borrow_mut(); let mut tree_borrows = alloc_extra.borrow_tracker_tb().borrow_mut();
// All reborrows incur a (possibly zero-sized) read access to the parent // All reborrows incur a (possibly zero-sized) read access to the parent
{ tree_borrows.perform_access(
let global = &this.machine.borrow_tracker.as_ref().unwrap(); AccessKind::Read,
let span = this.machine.current_span(); orig_tag,
tree_borrows.perform_access( range,
AccessKind::Read, this.machine.borrow_tracker.as_ref().unwrap(),
orig_tag, this.machine.current_span(),
range, diagnostics::AccessCause::Reborrow,
global, )?;
span, // Record the parent-child pair in the tree.
diagnostics::AccessCause::Reborrow, 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() { 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. Ok(Some(Provenance::Concrete { alloc_id, tag: new_tag }))
tree_borrows.new_child(orig_tag, new_tag, new_perm.initial_state, range, span)?;
Ok(Some((alloc_id, new_tag)))
} }
/// Retags an individual pointer, returning the retagged version. /// 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(); let new_tag = this.machine.borrow_tracker.as_mut().unwrap().get_mut().new_ptr();
// Compute the actual reborrow. // 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. // Adjust pointer.
let new_place = place.map_provenance(|p| { let new_place = place.map_provenance(|_| new_prov);
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
}
}
})
});
// Return new pointer. // Return new pointer.
Ok(ImmTy::from_immediate(new_place.to_ref(this), val.layout)) Ok(ImmTy::from_immediate(new_place.to_ref(this), val.layout))

View File

@ -134,25 +134,32 @@ pub struct PermTransition {
impl Permission { impl Permission {
/// Default initial permission of the root of a new tree. /// Default initial permission of the root of a new tree.
pub fn new_root() -> Self { pub fn new_active() -> Self {
Self { inner: Active } Self { inner: Active }
} }
/// Default initial permission of a reborrowed mutable reference. /// 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 } } 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 /// Default initial permission of a reborrowed shared reference
pub fn new_frozen() -> Self { pub fn new_frozen() -> Self {
Self { inner: Frozen } 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. /// Apply the transition to the inner PermissionPriv.
pub fn perform_access( pub fn perform_access(
kind: AccessKind, kind: AccessKind,
@ -438,7 +445,7 @@ mod propagation_optimization_checks {
} }
#[test] #[test]
fn foreign_read_is_noop_after_write() { fn foreign_read_is_noop_after_foreign_write() {
use transition::*; use transition::*;
let old_access = AccessKind::Write; let old_access = AccessKind::Write;
let new_access = AccessKind::Read; let new_access = AccessKind::Read;

View File

@ -110,7 +110,7 @@ impl LocationState {
// Helper to optimize the tree traversal. // Helper to optimize the tree traversal.
// The optimization here consists of observing thanks to the tests // 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 // that there are actually just three possible sequences of events that can occur
// in between two child accesses that produce different results. // 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) { let new_access_noop = match (self.latest_foreign_access, access_kind) {
// Previously applied transition makes the new one a guaranteed // Previously applied transition makes the new one a guaranteed
// noop in the two following cases: // 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, (Some(AccessKind::Write), AccessKind::Read) => true,
// (2) justified by `all_transitions_idempotent` // (2) justified by `all_transitions_idempotent`
(Some(old), new) if old == new => true, (Some(old), new) if old == new => true,
@ -376,7 +376,7 @@ where {
impl Tree { impl Tree {
/// Create a new tree, with only a root pointer. /// Create a new tree, with only a root pointer.
pub fn new(root_tag: BorTag, size: Size, span: Span) -> Self { 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 mut tag_mapping = UniKeyMap::default();
let root_idx = tag_mapping.insert(root_tag); let root_idx = tag_mapping.insert(root_tag);
let nodes = { let nodes = {
@ -670,7 +670,8 @@ impl AccessRelatedness {
mod commutation_tests { mod commutation_tests {
use super::*; use super::*;
impl LocationState { 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| { Permission::all().flat_map(|permission| {
[false, true].into_iter().map(move |initialized| { [false, true].into_iter().map(move |initialized| {
Self { permission, initialized, latest_foreign_access: None } 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 // Any protector state works, but we can't move reads across function boundaries
// so the two read accesses occur under the same protector. // so the two read accesses occur under the same protector.
for &protected in &[true, false] { 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 // Apply 1 then 2. Failure here means that there is UB in the source
// and we skip the check in the target. // and we skip the check in the target.
let mut loc12 = loc; let mut loc12 = loc;
let Ok(_) = loc12.perform_access(kind, rel1, protected) else { continue; }; 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, rel2, protected) else { continue };
// If 1 followed by 2 succeeded, then 2 followed by 1 must also succeed... // If 1 followed by 2 succeeded, then 2 followed by 1 must also succeed...
let mut loc21 = loc; 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. /// 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). /// 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. /// for your convenience.
fn call_function( fn call_function(
&mut self, &mut self,

View File

@ -427,7 +427,7 @@ pub struct MiriMachine<'mir, 'tcx> {
/// the emulated program. /// the emulated program.
profiler: Option<measureme::Profiler>, profiler: Option<measureme::Profiler>,
/// Used with `profiler` to cache the `StringId`s for event names /// Used with `profiler` to cache the `StringId`s for event names
/// uesd with `measureme`. /// used with `measureme`.
string_cache: FxHashMap<String, measureme::StringId>, string_cache: FxHashMap<String, measureme::StringId>,
/// Cache of `Instance` exported under the given `Symbol` name. /// Cache of `Instance` exported under the given `Symbol` name.
@ -516,7 +516,7 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> {
let pid = process::id(); let pid = process::id();
// We adopt the same naming scheme for the profiler output that rustc uses. In rustc, // 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 // 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. // control, we just pad for consistency with rustc.
let filename = format!("{crate_name}-{pid:07}"); let filename = format!("{crate_name}-{pid:07}");
let path = Path::new(out).join(filename); 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 // 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. // writes* during this call are insta-UB.
if ecx.machine.borrow_tracker.is_some() { 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)?; ecx.protect_place(&place)?;
} else { } else {
// Locals that don't have their address taken are as protected as they can ever be. // 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); 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 skip_ui_checks = env::var_os("MIRI_SKIP_UI_CHECKS").is_some();
let output_conflict_handling = match (bless, skip_ui_checks) { 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, (true, false) => OutputConflictHandling::Bless,
(false, true) => OutputConflictHandling::Ignore, (false, true) => OutputConflictHandling::Ignore,
(true, true) => panic!("cannot use RUSTC_BLESS and MIRI_SKIP_UI_CHECKS at the same time"), (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. //! Make sure that a retag acts like a write for the data race model.
//@revisions: stack tree
//@compile-flags: -Zmiri-preemption-rate=0 //@compile-flags: -Zmiri-preemption-rate=0
//@[tree]compile-flags: -Zmiri-tree-borrows
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
struct SendPtr(*mut u8); struct SendPtr(*mut u8);
@ -15,7 +17,7 @@ fn thread_1(p: SendPtr) {
fn thread_2(p: SendPtr) { fn thread_2(p: SendPtr) {
let p = p.0; let p = p.0;
unsafe { 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 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; LL | *p = 5;
| ^^^^^^ Data race detected between (1) Read on thread `<unnamed>` and (2) Write on thread `<unnamed>` at ALLOC. (2) just happened here | ^^^^^^ 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 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: 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 = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE (of the first span): = 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 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 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 // Check how a Reserved with interior mutability
// responds to a Foreign Write under a Protector // responds to a Foreign Write under a Protector
#[path = "../../../utils/mod.rs"] #[path = "../../../utils/mod.rs"]
#[macro_use]
mod utils; mod utils;
use utils::macros::*;
use std::cell::UnsafeCell; use std::cell::UnsafeCell;

View File

@ -1,8 +1,8 @@
//@compile-flags: -Zmiri-tree-borrows -Zmiri-tag-gc=0 //@compile-flags: -Zmiri-tree-borrows -Zmiri-tag-gc=0
#[path = "../../../utils/mod.rs"] #[path = "../../../utils/mod.rs"]
#[macro_use]
mod utils; mod utils;
use utils::macros::*;
// Check how a Reserved without interior mutability responds to a Foreign // Check how a Reserved without interior mutability responds to a Foreign
// Write when under a protector // 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)] #![feature(io_error_uncategorized)]
use std::convert::TryInto; 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::fs::{canonicalize, remove_dir_all, remove_file, File};
use std::io::{Error, ErrorKind, Write}; use std::io::{Error, ErrorKind, Write};
use std::os::unix::ffi::OsStrExt; use std::os::unix::ffi::OsStrExt;
use std::path::PathBuf; use std::path::PathBuf;
#[path = "../../utils/mod.rs"]
mod utils;
fn main() { fn main() {
test_dup_stdout_stderr(); test_dup_stdout_stderr();
test_canonicalize_too_long(); test_canonicalize_too_long();
@ -22,31 +25,9 @@ fn main() {
test_o_tmpfile_flag(); 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. /// Prepare: compute filename and make sure the file does not exist.
fn prepare(filename: &str) -> PathBuf { fn prepare(filename: &str) -> PathBuf {
let path = tmp().join(filename); let path = utils::tmp().join(filename);
// Clean the paths for robustness. // Clean the paths for robustness.
remove_file(&path).ok(); remove_file(&path).ok();
path path
@ -55,7 +36,7 @@ fn prepare(filename: &str) -> PathBuf {
/// Prepare directory: compute directory name and make sure it does not exist. /// Prepare directory: compute directory name and make sure it does not exist.
#[allow(unused)] #[allow(unused)]
fn prepare_dir(dirname: &str) -> PathBuf { fn prepare_dir(dirname: &str) -> PathBuf {
let path = tmp().join(&dirname); let path = utils::tmp().join(&dirname);
// Clean the directory for robustness. // Clean the directory for robustness.
remove_dir_all(&path).ok(); remove_dir_all(&path).ok();
path path

View File

@ -6,29 +6,8 @@ use std::fs::{remove_file, File};
use std::os::unix::io::AsRawFd; use std::os::unix::io::AsRawFd;
use std::path::PathBuf; use std::path::PathBuf;
fn tmp() -> PathBuf { #[path = "../../utils/mod.rs"]
use std::ffi::{c_char, CStr, CString}; mod utils;
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)
}
}
/// Test allocating variant of `realpath`. /// Test allocating variant of `realpath`.
fn test_posix_realpath_alloc() { fn test_posix_realpath_alloc() {
@ -38,7 +17,7 @@ fn test_posix_realpath_alloc() {
use std::os::unix::ffi::OsStringExt; use std::os::unix::ffi::OsStringExt;
let buf; 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"); let c_path = CString::new(path.as_os_str().as_bytes()).expect("CString::new failed");
// Cleanup before test. // Cleanup before test.
@ -63,7 +42,7 @@ fn test_posix_realpath_noalloc() {
use std::ffi::{CStr, CString}; use std::ffi::{CStr, CString};
use std::os::unix::ffi::OsStrExt; 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 c_path = CString::new(path.as_os_str().as_bytes()).expect("CString::new failed");
let mut v = vec![0; libc::PATH_MAX as usize]; let mut v = vec![0; libc::PATH_MAX as usize];
@ -103,7 +82,7 @@ fn test_posix_realpath_errors() {
fn test_posix_fadvise() { fn test_posix_fadvise() {
use std::io::Write; 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 // Cleanup before test
remove_file(&path).ok(); remove_file(&path).ok();
@ -130,7 +109,7 @@ fn test_posix_fadvise() {
fn test_sync_file_range() { fn test_sync_file_range() {
use std::io::Write; 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. // Cleanup before test.
remove_file(&path).ok(); remove_file(&path).ok();
@ -243,7 +222,7 @@ fn test_isatty() {
libc::isatty(libc::STDERR_FILENO); libc::isatty(libc::STDERR_FILENO);
// But when we open a file, it is definitely not a TTY. // 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. // Cleanup before test.
remove_file(&path).ok(); remove_file(&path).ok();
let file = File::create(&path).unwrap(); let file = File::create(&path).unwrap();

View File

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

View File

@ -5,7 +5,7 @@
#![feature(io_error_uncategorized)] #![feature(io_error_uncategorized)]
use std::collections::HashMap; use std::collections::HashMap;
use std::ffi::{c_char, OsString}; use std::ffi::OsString;
use std::fs::{ use std::fs::{
canonicalize, create_dir, read_dir, read_link, remove_dir, remove_dir_all, remove_file, rename, canonicalize, create_dir, read_dir, read_link, remove_dir, remove_dir_all, remove_file, rename,
File, OpenOptions, File, OpenOptions,
@ -13,6 +13,9 @@ use std::fs::{
use std::io::{Error, ErrorKind, IsTerminal, Read, Result, Seek, SeekFrom, Write}; use std::io::{Error, ErrorKind, IsTerminal, Read, Result, Seek, SeekFrom, Write};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
#[path = "../../utils/mod.rs"]
mod utils;
fn main() { fn main() {
test_path_conversion(); test_path_conversion();
test_file(); test_file();
@ -30,37 +33,9 @@ fn main() {
test_from_raw_os_error(); 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. /// Prepare: compute filename and make sure the file does not exist.
fn prepare(filename: &str) -> PathBuf { fn prepare(filename: &str) -> PathBuf {
let path = tmp().join(filename); let path = utils::tmp().join(filename);
// Clean the paths for robustness. // Clean the paths for robustness.
remove_file(&path).ok(); remove_file(&path).ok();
path path
@ -68,7 +43,7 @@ fn prepare(filename: &str) -> PathBuf {
/// Prepare directory: compute directory name and make sure it does not exist. /// Prepare directory: compute directory name and make sure it does not exist.
fn prepare_dir(dirname: &str) -> PathBuf { fn prepare_dir(dirname: &str) -> PathBuf {
let path = tmp().join(&dirname); let path = utils::tmp().join(&dirname);
// Clean the directory for robustness. // Clean the directory for robustness.
remove_dir_all(&path).ok(); remove_dir_all(&path).ok();
path path
@ -83,7 +58,7 @@ fn prepare_with_content(filename: &str, content: &[u8]) -> PathBuf {
} }
fn test_path_conversion() { fn test_path_conversion() {
let tmp = tmp(); let tmp = utils::tmp();
assert!(tmp.is_absolute(), "{:?} is not absolute", tmp); assert!(tmp.is_absolute(), "{:?} is not absolute", tmp);
assert!(tmp.is_dir(), "{:?} is not a directory", tmp); assert!(tmp.is_dir(), "{:?} is not a directory", tmp);
} }

View File

@ -10,7 +10,7 @@ fn main() {
mut_raw_mut(); mut_raw_mut();
partially_invalidate_mut(); partially_invalidate_mut();
drop_after_sharing(); drop_after_sharing();
direct_mut_to_const_raw(); // direct_mut_to_const_raw();
two_raw(); two_raw();
shr_and_raw(); shr_and_raw();
disjoint_mutable_subborrows(); disjoint_mutable_subborrows();
@ -19,6 +19,7 @@ fn main() {
mut_below_shr(); mut_below_shr();
wide_raw_ptr_in_tuple(); wide_raw_ptr_in_tuple();
not_unpin_not_protected(); not_unpin_not_protected();
write_does_not_invalidate_all_aliases();
} }
// Make sure that reading from an `&mut` does, like reborrowing to `&`, // 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. // 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>
// 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 x = &mut 0;
let y: *const i32 = x; let y: *const i32 = x;
unsafe { *(y as *mut i32) = 1; } unsafe { *(y as *mut i32) = 1; }
assert_eq!(*x, 1); assert_eq!(*x, 1);
*/ }*/
}
// Make sure that we can create two raw pointers from a mutable reference and use them both. // Make sure that we can create two raw pointers from a mutable reference and use them both.
fn two_raw() { fn two_raw() {
@ -238,3 +238,28 @@ fn not_unpin_not_protected() {
drop(unsafe { Box::from_raw(raw) }); 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 //@compile-flags: -Zmiri-tree-borrows -Zmiri-tag-gc=0
#[path = "../../utils/mod.rs"] #[path = "../../utils/mod.rs"]
#[macro_use]
mod utils; mod utils;
use utils::macros::*;
use std::cell::UnsafeCell; use std::cell::UnsafeCell;

View File

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

View File

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

View File

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

View File

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

View File

@ -10,6 +10,8 @@ fn main() {
aliasing_read_only_mutable_refs(); aliasing_read_only_mutable_refs();
string_as_mut_ptr(); string_as_mut_ptr();
two_mut_protected_same_alloc(); two_mut_protected_same_alloc();
direct_mut_to_const_raw();
local_addr_of_mut();
// Stacked Borrows tests // Stacked Borrows tests
read_does_not_invalidate1(); read_does_not_invalidate1();
@ -19,7 +21,6 @@ fn main() {
mut_raw_mut(); mut_raw_mut();
partially_invalidate_mut(); partially_invalidate_mut();
drop_after_sharing(); drop_after_sharing();
direct_mut_to_const_raw();
two_raw(); two_raw();
shr_and_raw(); shr_and_raw();
disjoint_mutable_subborrows(); disjoint_mutable_subborrows();
@ -28,6 +29,18 @@ fn main() {
mut_below_shr(); mut_below_shr();
wide_raw_ptr_in_tuple(); wide_raw_ptr_in_tuple();
not_unpin_not_protected(); 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 // 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. // Make sure that coercing &mut T to *const T produces a writeable pointer.
fn direct_mut_to_const_raw() { 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; let y: *const i32 = x;
unsafe { *(y as *mut i32) = 1; } unsafe {
*(y as *mut i32) = 1;
}
assert_eq!(*x, 1); assert_eq!(*x, 1);
*/
} }
// Make sure that we can create two raw pointers from a mutable reference and use them both. // 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) }); 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)] #![feature(ptr_internals)]
#[path = "../../utils/mod.rs"] #[path = "../../utils/mod.rs"]
#[macro_use]
mod utils; mod utils;
use utils::macros::*;
use core::ptr::Unique; use core::ptr::Unique;

View File

@ -5,8 +5,8 @@
#![feature(vec_into_raw_parts)] #![feature(vec_into_raw_parts)]
#[path = "../../utils/mod.rs"] #[path = "../../utils/mod.rs"]
#[macro_use]
mod utils; mod utils;
use utils::macros::*;
// Check general handling of `Unique`: // Check general handling of `Unique`:
// there is no *explicit* `Unique` being used here, but there is one // 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!`. /// The id obtained can be passed directly to `print_state!`.
macro_rules! alloc_id { macro_rules! alloc_id {
($ptr:expr) => { ($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`. /// tags that have not been given a name. Defaults to `false`.
macro_rules! print_state { macro_rules! print_state {
($alloc_id:expr) => { ($alloc_id:expr) => {
crate::utils::macros::print_state!($alloc_id, false); print_state!($alloc_id, false);
}; };
($alloc_id:expr, $show:expr) => { ($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. /// `stringify!($ptr)` the name of `ptr` in the source code.
macro_rules! name { macro_rules! name {
($ptr:expr, $name:expr) => { ($ptr:expr, $name:expr) => {
crate::utils::macros::name!($ptr => 0, $name); name!($ptr => 0, $name);
}; };
($ptr:expr) => { ($ptr:expr) => {
crate::utils::macros::name!($ptr => 0, stringify!($ptr)); name!($ptr => 0, stringify!($ptr));
}; };
($ptr:expr => $nth_parent:expr) => { ($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) => { ($ptr:expr => $nth_parent:expr, $name:expr) => {
let name = $name.as_bytes(); 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)] #[repr(C)]
/// Layout of the return value of `miri_resolve_frame`, /// Layout of the return value of `miri_resolve_frame`,
/// with fields in the exact same order. /// with fields in the exact same order.

View File

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