mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-22 14:55:26 +00:00
commit
adb15a20ac
2
src/tools/miri/.github/workflows/ci.yml
vendored
2
src/tools/miri/.github/workflows/ci.yml
vendored
@ -130,7 +130,7 @@ jobs:
|
||||
- name: clippy
|
||||
run: ./miri clippy -- -D warnings
|
||||
- name: rustdoc
|
||||
run: RUSTDOCFLAGS="-Dwarnings" cargo doc --document-private-items
|
||||
run: RUSTDOCFLAGS="-Dwarnings" ./miri cargo doc --document-private-items
|
||||
|
||||
# These jobs doesn't actually test anything, but they're only used to tell
|
||||
# bors the build completed, as there is no practical way to detect when a
|
||||
|
@ -107,7 +107,7 @@ evaluation error was originally raised.
|
||||
### UI testing
|
||||
|
||||
We use ui-testing in Miri, meaning we generate `.stderr` and `.stdout` files for the output
|
||||
produced by Miri. You can use `./miri bless` to automatically (re)generate these files when
|
||||
produced by Miri. You can use `./miri test --bless` to automatically (re)generate these files when
|
||||
you add new tests or change how Miri presents certain output.
|
||||
|
||||
Note that when you also use `MIRIFLAGS` to change optimizations and similar, the ui output
|
||||
|
@ -56,7 +56,7 @@ fn main() {
|
||||
return;
|
||||
}
|
||||
|
||||
// The way rustdoc invokes rustc is indistuingishable from the way cargo invokes rustdoc by the
|
||||
// The way rustdoc invokes rustc is indistinguishable from the way cargo invokes rustdoc by the
|
||||
// arguments alone. `phase_cargo_rustdoc` sets this environment variable to let us disambiguate.
|
||||
if env::var_os("MIRI_CALLED_FROM_RUSTDOC").is_some() {
|
||||
// ...however, we then also see this variable when rustdoc invokes us as the testrunner!
|
||||
|
@ -94,7 +94,7 @@ pub fn phase_cargo_miri(mut args: impl Iterator<Item = String>) {
|
||||
let target = target.as_ref().unwrap_or(host);
|
||||
|
||||
// We always setup.
|
||||
setup(&subcommand, target, &rustc_version, verbose);
|
||||
let miri_sysroot = setup(&subcommand, target, &rustc_version, verbose);
|
||||
|
||||
// Invoke actual cargo for the job, but with different flags.
|
||||
// We re-use `cargo test` and `cargo run`, which makes target and binary handling very easy but
|
||||
@ -159,6 +159,8 @@ pub fn phase_cargo_miri(mut args: impl Iterator<Item = String>) {
|
||||
// Forward all further arguments (not consumed by `ArgSplitFlagValue`) to cargo.
|
||||
cmd.args(args);
|
||||
|
||||
// Let it know where the Miri sysroot lives.
|
||||
cmd.env("MIRI_SYSROOT", miri_sysroot);
|
||||
// Set `RUSTC_WRAPPER` to ourselves. Cargo will prepend that binary to its usual invocation,
|
||||
// i.e., the first argument is `rustc` -- which is what we use in `main` to distinguish
|
||||
// the two codepaths. (That extra argument is why we prefer this over setting `RUSTC`.)
|
||||
@ -519,7 +521,7 @@ pub fn phase_runner(mut binary_args: impl Iterator<Item = String>, phase: Runner
|
||||
// `.rmeta`.
|
||||
// We also need to remove `--error-format` as cargo specifies that to be JSON,
|
||||
// but when we run here, cargo does not interpret the JSON any more. `--json`
|
||||
// then also nees to be dropped.
|
||||
// then also needs to be dropped.
|
||||
let mut args = info.args.into_iter();
|
||||
let error_format_flag = "--error-format";
|
||||
let json_flag = "--json";
|
||||
@ -538,8 +540,7 @@ pub fn phase_runner(mut binary_args: impl Iterator<Item = String>, phase: Runner
|
||||
}
|
||||
// Respect `MIRIFLAGS`.
|
||||
if let Ok(a) = env::var("MIRIFLAGS") {
|
||||
// This code is taken from `RUSTFLAGS` handling in cargo.
|
||||
let args = a.split(' ').map(str::trim).filter(|s| !s.is_empty()).map(str::to_string);
|
||||
let args = flagsplit(&a);
|
||||
cmd.args(args);
|
||||
}
|
||||
|
||||
|
@ -13,13 +13,20 @@ use crate::util::*;
|
||||
/// Performs the setup required to make `cargo miri` work: Getting a custom-built libstd. Then sets
|
||||
/// `MIRI_SYSROOT`. Skipped if `MIRI_SYSROOT` is already set, in which case we expect the user has
|
||||
/// done all this already.
|
||||
pub fn setup(subcommand: &MiriCommand, target: &str, rustc_version: &VersionMeta, verbose: usize) {
|
||||
pub fn setup(
|
||||
subcommand: &MiriCommand,
|
||||
target: &str,
|
||||
rustc_version: &VersionMeta,
|
||||
verbose: usize,
|
||||
) -> PathBuf {
|
||||
let only_setup = matches!(subcommand, MiriCommand::Setup);
|
||||
let ask_user = !only_setup;
|
||||
let print_sysroot = only_setup && has_arg_flag("--print-sysroot"); // whether we just print the sysroot path
|
||||
if !only_setup && std::env::var_os("MIRI_SYSROOT").is_some() {
|
||||
// Skip setup step if MIRI_SYSROOT is explicitly set, *unless* we are `cargo miri setup`.
|
||||
return;
|
||||
if !only_setup {
|
||||
if let Some(sysroot) = std::env::var_os("MIRI_SYSROOT") {
|
||||
// Skip setup step if MIRI_SYSROOT is explicitly set, *unless* we are `cargo miri setup`.
|
||||
return sysroot.into();
|
||||
}
|
||||
}
|
||||
|
||||
// Determine where the rust sources are located. The env var trumps auto-detection.
|
||||
@ -92,6 +99,8 @@ pub fn setup(subcommand: &MiriCommand, target: &str, rustc_version: &VersionMeta
|
||||
command.env("RUSTC", &cargo_miri_path);
|
||||
}
|
||||
command.env("MIRI_CALLED_FROM_SETUP", "1");
|
||||
// Miri expects `MIRI_SYSROOT` to be set when invoked in target mode. Even if that directory is empty.
|
||||
command.env("MIRI_SYSROOT", &sysroot_dir);
|
||||
// Make sure there are no other wrappers getting in our way (Cc
|
||||
// https://github.com/rust-lang/miri/issues/1421,
|
||||
// https://github.com/rust-lang/miri/issues/2429). Looks like setting
|
||||
@ -105,7 +114,7 @@ pub fn setup(subcommand: &MiriCommand, target: &str, rustc_version: &VersionMeta
|
||||
command.arg("-v");
|
||||
}
|
||||
} else {
|
||||
// Supress output.
|
||||
// Suppress output.
|
||||
command.stdout(process::Stdio::null());
|
||||
command.stderr(process::Stdio::null());
|
||||
}
|
||||
@ -117,8 +126,6 @@ pub fn setup(subcommand: &MiriCommand, target: &str, rustc_version: &VersionMeta
|
||||
// the user might have set, which is consistent with normal `cargo build` that does
|
||||
// not apply `RUSTFLAGS` to the sysroot either.
|
||||
let rustflags = &["-Cdebug-assertions=off", "-Coverflow-checks=on"];
|
||||
// Make sure all target-level Miri invocations know their sysroot.
|
||||
std::env::set_var("MIRI_SYSROOT", &sysroot_dir);
|
||||
|
||||
// Do the build.
|
||||
if print_sysroot {
|
||||
@ -159,4 +166,6 @@ pub fn setup(subcommand: &MiriCommand, target: &str, rustc_version: &VersionMeta
|
||||
// Print just the sysroot and nothing else to stdout; this way we do not need any escaping.
|
||||
println!("{}", sysroot_dir.display());
|
||||
}
|
||||
|
||||
sysroot_dir
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::ffi::OsString;
|
||||
use std::fmt::Write as _;
|
||||
use std::fs::File;
|
||||
use std::io::{self, BufWriter, Read, Write};
|
||||
use std::ops::Not;
|
||||
@ -114,6 +112,11 @@ pub fn cargo() -> Command {
|
||||
Command::new(env::var_os("CARGO").unwrap_or_else(|| OsString::from("cargo")))
|
||||
}
|
||||
|
||||
pub fn flagsplit(flags: &str) -> Vec<String> {
|
||||
// This code is taken from `RUSTFLAGS` handling in cargo.
|
||||
flags.split(' ').map(str::trim).filter(|s| !s.is_empty()).map(str::to_string).collect()
|
||||
}
|
||||
|
||||
/// Execute the `Command`, where possible by replacing the current process with a new process
|
||||
/// described by the `Command`. Then exit this process with the exit code of the new process.
|
||||
pub fn exec(mut cmd: Command) -> ! {
|
||||
@ -242,46 +245,10 @@ pub fn local_crates(metadata: &Metadata) -> String {
|
||||
local_crates
|
||||
}
|
||||
|
||||
fn env_vars_from_cmd(cmd: &Command) -> Vec<(String, String)> {
|
||||
let mut envs = HashMap::new();
|
||||
for (key, value) in std::env::vars() {
|
||||
envs.insert(key, value);
|
||||
}
|
||||
for (key, value) in cmd.get_envs() {
|
||||
if let Some(value) = value {
|
||||
envs.insert(key.to_string_lossy().to_string(), value.to_string_lossy().to_string());
|
||||
} else {
|
||||
envs.remove(&key.to_string_lossy().to_string());
|
||||
}
|
||||
}
|
||||
let mut envs: Vec<_> = envs.into_iter().collect();
|
||||
envs.sort();
|
||||
envs
|
||||
}
|
||||
|
||||
/// Debug-print a command that is going to be run.
|
||||
pub fn debug_cmd(prefix: &str, verbose: usize, cmd: &Command) {
|
||||
if verbose == 0 {
|
||||
return;
|
||||
}
|
||||
// We only do a single `eprintln!` call to minimize concurrency interactions.
|
||||
let mut out = prefix.to_string();
|
||||
writeln!(out, " running command: env \\").unwrap();
|
||||
if verbose > 1 {
|
||||
// Print the full environment this will be called in.
|
||||
for (key, value) in env_vars_from_cmd(cmd) {
|
||||
writeln!(out, "{key}={value:?} \\").unwrap();
|
||||
}
|
||||
} else {
|
||||
// Print only what has been changed for this `cmd`.
|
||||
for (var, val) in cmd.get_envs() {
|
||||
if let Some(val) = val {
|
||||
writeln!(out, "{}={val:?} \\", var.to_string_lossy()).unwrap();
|
||||
} else {
|
||||
writeln!(out, "--unset={}", var.to_string_lossy()).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
write!(out, "{cmd:?}").unwrap();
|
||||
eprintln!("{out}");
|
||||
eprintln!("{prefix} running command: {cmd:?}");
|
||||
}
|
||||
|
@ -1,359 +1,6 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
USAGE=$(cat <<"EOF"
|
||||
COMMANDS
|
||||
|
||||
./miri install <flags>:
|
||||
Installs the miri driver and cargo-miri. <flags> are passed to `cargo
|
||||
install`. Sets up the rpath such that the installed binary should work in any
|
||||
working directory. Note that the binaries are placed in the `miri` toolchain
|
||||
sysroot, to prevent conflicts with other toolchains.
|
||||
|
||||
./miri build <flags>:
|
||||
Just build miri. <flags> are passed to `cargo build`.
|
||||
|
||||
./miri check <flags>:
|
||||
Just check miri. <flags> are passed to `cargo check`.
|
||||
|
||||
./miri test <flags>:
|
||||
Build miri, set up a sysroot and then run the test suite. <flags> are passed
|
||||
to the final `cargo test` invocation.
|
||||
|
||||
./miri run <flags>:
|
||||
Build miri, set up a sysroot and then run the driver with the given <flags>.
|
||||
(Also respects MIRIFLAGS environment variable.)
|
||||
|
||||
./miri fmt <flags>:
|
||||
Format all sources and tests. <flags> are passed to `rustfmt`.
|
||||
|
||||
./miri clippy <flags>:
|
||||
Runs clippy on all sources. <flags> are passed to `cargo clippy`.
|
||||
|
||||
./miri cargo <flags>:
|
||||
Runs just `cargo <flags>` with the Miri-specific environment variables.
|
||||
Mainly meant to be invoked by rust-analyzer.
|
||||
|
||||
./miri many-seeds <command>:
|
||||
Runs <command> over and over again with different seeds for Miri. The MIRIFLAGS
|
||||
variable is set to its original value appended with ` -Zmiri-seed=$SEED` for
|
||||
many different seeds. The MIRI_SEEDS variable controls how many seeds are being
|
||||
tried; MIRI_SEED_START controls the first seed to try.
|
||||
|
||||
./miri bench <benches>:
|
||||
Runs the benchmarks from bench-cargo-miri in hyperfine. hyperfine needs to be installed.
|
||||
<benches> can explicitly list the benchmarks to run; by default, all of them are run.
|
||||
|
||||
./miri toolchain <flags>:
|
||||
Update and activate the rustup toolchain 'miri' to the commit given in the
|
||||
`rust-version` file.
|
||||
`rustup-toolchain-install-master` must be installed for this to work. Any extra
|
||||
flags are passed to `rustup-toolchain-install-master`.
|
||||
|
||||
./miri rustc-pull <commit>:
|
||||
Pull and merge Miri changes from the rustc repo. Defaults to fetching the latest
|
||||
rustc commit. The fetched commit is stored in the `rust-version` file, so the
|
||||
next `./miri toolchain` will install the rustc that just got pulled.
|
||||
|
||||
./miri rustc-push <github user> <branch>:
|
||||
Push Miri changes back to the rustc repo. This will pull a copy of the rustc
|
||||
history into the Miri repo, unless you set the RUSTC_GIT env var to an existing
|
||||
clone of the rustc repo.
|
||||
|
||||
ENVIRONMENT VARIABLES
|
||||
|
||||
MIRI_SYSROOT:
|
||||
If already set, the "sysroot setup" step is skipped.
|
||||
|
||||
CARGO_EXTRA_FLAGS:
|
||||
Pass extra flags to all cargo invocations. (Ignored by `./miri cargo`.)
|
||||
EOF
|
||||
)
|
||||
|
||||
## We need to know which command to run and some global constants.
|
||||
COMMAND="$1"
|
||||
if [ -z "$COMMAND" ]; then
|
||||
echo "$USAGE"
|
||||
exit 1
|
||||
fi
|
||||
shift
|
||||
# macOS does not have a useful readlink/realpath so we have to use Python instead...
|
||||
MIRIDIR=$(python3 -c 'import pathlib, sys; print(pathlib.Path(sys.argv[1]).resolve().parent.as_posix())' "$0")
|
||||
# Used for rustc syncs.
|
||||
JOSH_FILTER=":rev(75dd959a3a40eb5b4574f8d2e23aa6efbeb33573:prefix=src/tools/miri):/src/tools/miri"
|
||||
# Needed for `./miri bench`.
|
||||
TOOLCHAIN=$(cd "$MIRIDIR"; rustup show active-toolchain | head -n 1 | cut -d ' ' -f 1)
|
||||
|
||||
## Early commands, that don't do auto-things and don't want the environment-altering things happening below.
|
||||
case "$COMMAND" in
|
||||
toolchain)
|
||||
cd "$MIRIDIR"
|
||||
NEW_COMMIT=$(cat rust-version)
|
||||
# Make sure rustup-toolchain-install-master is installed.
|
||||
if ! which rustup-toolchain-install-master >/dev/null; then
|
||||
echo "Please install rustup-toolchain-install-master by running 'cargo install rustup-toolchain-install-master'"
|
||||
exit 1
|
||||
fi
|
||||
# Check if we already are at that commit.
|
||||
CUR_COMMIT=$(rustc +miri --version -v 2>/dev/null | grep "^commit-hash: " | cut -d " " -f 2)
|
||||
if [[ "$CUR_COMMIT" == "$NEW_COMMIT" ]]; then
|
||||
echo "miri toolchain is already at commit $CUR_COMMIT."
|
||||
if [[ "$TOOLCHAIN" != "miri" ]]; then
|
||||
rustup override set miri
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
# Install and setup new toolchain.
|
||||
rustup toolchain uninstall miri
|
||||
rustup-toolchain-install-master -n miri -c cargo -c rust-src -c rustc-dev -c llvm-tools -c rustfmt -c clippy "$@" -- "$NEW_COMMIT"
|
||||
rustup override set miri
|
||||
# Cleanup.
|
||||
cargo clean
|
||||
# Call 'cargo metadata' on the sources in case that changes the lockfile
|
||||
# (which fails under some setups when it is done from inside vscode).
|
||||
cargo metadata --format-version 1 --manifest-path "$(rustc --print sysroot)/lib/rustlib/rustc-src/rust/compiler/rustc/Cargo.toml" >/dev/null
|
||||
# Done!
|
||||
exit 0
|
||||
;;
|
||||
rustc-pull)
|
||||
cd "$MIRIDIR"
|
||||
FETCH_COMMIT="$1"
|
||||
if [ -z "$FETCH_COMMIT" ]; then
|
||||
FETCH_COMMIT=$(git ls-remote https://github.com/rust-lang/rust/ HEAD | cut -f 1)
|
||||
fi
|
||||
# Update rust-version file. As a separate commit, since making it part of
|
||||
# the merge has confused the heck out of josh in the past.
|
||||
echo "$FETCH_COMMIT" > rust-version
|
||||
git commit rust-version -m "Preparing for merge from rustc" || (echo "FAILED to commit rust-version file, something went wrong"; exit 1)
|
||||
# Fetch given rustc commit and note down which one that was
|
||||
git fetch http://localhost:8000/rust-lang/rust.git@$FETCH_COMMIT$JOSH_FILTER.git || (echo "FAILED to fetch new commits, something went wrong"; exit 1)
|
||||
git merge FETCH_HEAD --no-ff -m "Merge from rustc" || (echo "FAILED to merge new commits ($(git rev-parse FETCH_HEAD)), something went wrong"; exit 1)
|
||||
exit 0
|
||||
;;
|
||||
rustc-push)
|
||||
USER="$1"
|
||||
BRANCH="$2"
|
||||
if [ -z "$USER" ] || [ -z "$BRANCH" ]; then
|
||||
echo "Usage: $0 rustc-push <github user> <branch>"
|
||||
exit 1
|
||||
fi
|
||||
if [ -n "$RUSTC_GIT" ]; then
|
||||
# Use an existing fork for the branch updates.
|
||||
cd "$RUSTC_GIT"
|
||||
else
|
||||
# Do this in the local Miri repo.
|
||||
echo "This will pull a copy of the rust-lang/rust history into this Miri checkout, growing it by about 1GB."
|
||||
read -r -p "To avoid that, abort now and set the RUSTC_GIT environment variable to an existing rustc checkout. Proceed? [y/N] "
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
exit 1
|
||||
fi
|
||||
cd "$MIRIDIR"
|
||||
fi
|
||||
# Prepare the branch. Pushing works much better if we use as base exactly
|
||||
# the commit that we pulled from last time, so we use the `rust-version`
|
||||
# file as a good approximation of that.
|
||||
BASE=$(cat "$MIRIDIR/rust-version")
|
||||
echo "Preparing $USER/rust (base: $BASE)..."
|
||||
if git fetch "https://github.com/$USER/rust" "$BRANCH" &>/dev/null; then
|
||||
echo "The branch '$BRANCH' seems to already exist in 'https://github.com/$USER/rust'. Please delete it and try again."
|
||||
exit 1
|
||||
fi
|
||||
git fetch https://github.com/rust-lang/rust $BASE
|
||||
git push https://github.com/$USER/rust $BASE:refs/heads/$BRANCH -f
|
||||
echo
|
||||
# Do the actual push.
|
||||
cd "$MIRIDIR"
|
||||
echo "Pushing Miri changes..."
|
||||
git push http://localhost:8000/$USER/rust.git$JOSH_FILTER.git HEAD:$BRANCH
|
||||
# Do a round-trip check to make sure the push worked as expected.
|
||||
echo
|
||||
git fetch http://localhost:8000/$USER/rust.git@$JOSH_FILTER.git $BRANCH &>/dev/null
|
||||
if [[ $(git rev-parse HEAD) != $(git rev-parse FETCH_HEAD) ]]; then
|
||||
echo "ERROR: Josh created a non-roundtrip push! Do NOT merge this into rustc!"
|
||||
exit 1
|
||||
else
|
||||
echo "Confirmed that the push round-trips back to Miri properly. Please create a rustc PR:"
|
||||
echo " https://github.com/$USER/rust/pull/new/$BRANCH"
|
||||
exit 0
|
||||
fi
|
||||
;;
|
||||
many-seeds)
|
||||
MIRI_SEED_START=${MIRI_SEED_START:-0} # default to 0
|
||||
MIRI_SEEDS=${MIRI_SEEDS:-256} # default to 256
|
||||
for SEED in $(seq $MIRI_SEED_START $(( $MIRI_SEED_START + $MIRI_SEEDS - 1 )) ); do
|
||||
echo "Trying seed: $SEED"
|
||||
MIRIFLAGS="$MIRIFLAGS -Zlayout-seed=$SEED -Zmiri-seed=$SEED" $@ || { echo "Failing seed: $SEED"; break; }
|
||||
done
|
||||
exit 0
|
||||
;;
|
||||
bench)
|
||||
# The hyperfine to use
|
||||
HYPERFINE=${HYPERFINE:-hyperfine -w 1 -m 5 --shell=none}
|
||||
# Make sure we have an up-to-date Miri installed
|
||||
"$0" install
|
||||
# Run the requested benchmarks
|
||||
if [ -z "${1+exists}" ]; then
|
||||
BENCHES=( $(ls "$MIRIDIR/bench-cargo-miri" ) )
|
||||
else
|
||||
BENCHES=("$@")
|
||||
fi
|
||||
for BENCH in "${BENCHES[@]}"; do
|
||||
$HYPERFINE "cargo +$TOOLCHAIN miri run --manifest-path $MIRIDIR/bench-cargo-miri/$BENCH/Cargo.toml"
|
||||
done
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
## Run the auto-things.
|
||||
if [ -z "$MIRI_AUTO_OPS" ]; then
|
||||
export MIRI_AUTO_OPS=42
|
||||
|
||||
# Run this first, so that the toolchain doesn't change after
|
||||
# other code has run.
|
||||
if [ -f "$MIRIDIR/.auto-everything" ] || [ -f "$MIRIDIR/.auto-toolchain" ] ; then
|
||||
$0 toolchain
|
||||
# Let's make sure to actually use that toolchain, too.
|
||||
TOOLCHAIN=miri
|
||||
fi
|
||||
|
||||
if [ -f "$MIRIDIR/.auto-everything" ] || [ -f "$MIRIDIR/.auto-fmt" ] ; then
|
||||
$0 fmt
|
||||
fi
|
||||
|
||||
if [ -f "$MIRIDIR/.auto-everything" ] || [ -f "$MIRIDIR/.auto-clippy" ] ; then
|
||||
$0 clippy -- -D warnings
|
||||
fi
|
||||
fi
|
||||
|
||||
## Prepare the environment
|
||||
# Determine some toolchain properties
|
||||
TARGET=$(rustc +$TOOLCHAIN --version --verbose | grep "^host:" | cut -d ' ' -f 2)
|
||||
SYSROOT=$(rustc +$TOOLCHAIN --print sysroot)
|
||||
LIBDIR=$SYSROOT/lib/rustlib/$TARGET/lib
|
||||
if ! test -d "$LIBDIR"; then
|
||||
echo "Something went wrong determining the library dir."
|
||||
echo "I got $LIBDIR but that does not exist."
|
||||
echo "Please report a bug at https://github.com/rust-lang/miri/issues."
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# Prepare flags for cargo and rustc.
|
||||
CARGO="cargo +$TOOLCHAIN"
|
||||
# Share target dir between `miri` and `cargo-miri`.
|
||||
if [ -z "$CARGO_TARGET_DIR" ]; then
|
||||
export CARGO_TARGET_DIR="$MIRIDIR/target"
|
||||
fi
|
||||
# We configure dev builds to not be unusably slow.
|
||||
if [ -z "$CARGO_PROFILE_DEV_OPT_LEVEL" ]; then
|
||||
export CARGO_PROFILE_DEV_OPT_LEVEL=2
|
||||
fi
|
||||
# Enable rustc-specific lints (ignored without `-Zunstable-options`).
|
||||
export RUSTFLAGS="-Zunstable-options -Wrustc::internal -Wrust_2018_idioms -Wunused_lifetimes -Wsemicolon_in_expressions_from_macros $RUSTFLAGS"
|
||||
# We set the rpath so that Miri finds the private rustc libraries it needs.
|
||||
export RUSTFLAGS="-C link-args=-Wl,-rpath,$LIBDIR $RUSTFLAGS"
|
||||
|
||||
## Helper functions
|
||||
|
||||
# Build a sysroot and set MIRI_SYSROOT to use it. Arguments are passed to `cargo miri setup`.
|
||||
build_sysroot() {
|
||||
if ! MIRI_SYSROOT="$($CARGO run $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/cargo-miri/Cargo.toml -- miri setup --print-sysroot "$@")"; then
|
||||
# Run it again so the user can see the error.
|
||||
$CARGO run $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/cargo-miri/Cargo.toml -- miri setup "$@"
|
||||
echo "'cargo miri setup' failed"
|
||||
exit 1
|
||||
fi
|
||||
export MIRI_SYSROOT
|
||||
}
|
||||
|
||||
# Prepare and set MIRI_SYSROOT. Respects `MIRI_TEST_TARGET` and takes into account
|
||||
# locally built vs. distributed rustc.
|
||||
find_sysroot() {
|
||||
if [ -n "$MIRI_SYSROOT" ]; then
|
||||
# Sysroot already set, use that.
|
||||
return 0
|
||||
fi
|
||||
# We need to build a sysroot.
|
||||
if [ -n "$MIRI_TEST_TARGET" ]; then
|
||||
build_sysroot --target "$MIRI_TEST_TARGET"
|
||||
else
|
||||
build_sysroot
|
||||
fi
|
||||
}
|
||||
|
||||
## Main
|
||||
|
||||
# Run command.
|
||||
case "$COMMAND" in
|
||||
install)
|
||||
# Install binaries to the miri toolchain's sysroot so they do not interact with other toolchains.
|
||||
$CARGO install $CARGO_EXTRA_FLAGS --path "$MIRIDIR" --force --root "$SYSROOT" "$@"
|
||||
$CARGO install $CARGO_EXTRA_FLAGS --path "$MIRIDIR"/cargo-miri --force --root "$SYSROOT" "$@"
|
||||
;;
|
||||
check)
|
||||
# Check, and let caller control flags.
|
||||
$CARGO check $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml --all-targets "$@"
|
||||
$CARGO check $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/cargo-miri/Cargo.toml "$@"
|
||||
;;
|
||||
build)
|
||||
# Build, and let caller control flags.
|
||||
$CARGO build $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml "$@"
|
||||
$CARGO build $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/cargo-miri/Cargo.toml "$@"
|
||||
;;
|
||||
test|bless)
|
||||
# First build and get a sysroot.
|
||||
$CARGO build $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml
|
||||
find_sysroot
|
||||
if [ "$COMMAND" = "bless" ]; then
|
||||
export RUSTC_BLESS="Gesundheit"
|
||||
fi
|
||||
# Then test, and let caller control flags.
|
||||
# Only in root project as `cargo-miri` has no tests.
|
||||
$CARGO test $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml "$@"
|
||||
;;
|
||||
run|run-dep)
|
||||
# Scan for "--target" to overwrite the "MIRI_TEST_TARGET" env var so
|
||||
# that we set the MIRI_SYSROOT up the right way.
|
||||
FOUND_TARGET_OPT=0
|
||||
for ARG in "$@"; do
|
||||
if [ "$LAST_ARG" = "--target" ]; then
|
||||
# Found it!
|
||||
export MIRI_TEST_TARGET="$ARG"
|
||||
FOUND_TARGET_OPT=1
|
||||
break
|
||||
fi
|
||||
LAST_ARG="$ARG"
|
||||
done
|
||||
if [ "$FOUND_TARGET_OPT" = "0" ] && [ -n "$MIRI_TEST_TARGET" ]; then
|
||||
# Make sure Miri actually uses this target.
|
||||
MIRIFLAGS="$MIRIFLAGS --target $MIRI_TEST_TARGET"
|
||||
fi
|
||||
|
||||
CARGO="$CARGO --quiet"
|
||||
# First build and get a sysroot.
|
||||
$CARGO build $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml
|
||||
find_sysroot
|
||||
# Then run the actual command.
|
||||
|
||||
if [ "$COMMAND" = "run-dep" ]; then
|
||||
exec $CARGO test --test compiletest $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml -- --miri-run-dep-mode $MIRIFLAGS "$@"
|
||||
else
|
||||
exec $CARGO run $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml -- $MIRIFLAGS "$@"
|
||||
fi
|
||||
;;
|
||||
fmt)
|
||||
find "$MIRIDIR" -not \( -name target -prune \) -name '*.rs' \
|
||||
| xargs rustfmt +$TOOLCHAIN --edition=2021 --config-path "$MIRIDIR/rustfmt.toml" "$@"
|
||||
;;
|
||||
clippy)
|
||||
$CARGO clippy $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml --all-targets "$@"
|
||||
$CARGO clippy $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/cargo-miri/Cargo.toml "$@"
|
||||
;;
|
||||
cargo)
|
||||
# We carefully kept the working dir intact, so this will run cargo *on the workspace in the
|
||||
# current working dir*, not on the main Miri workspace. That is exactly what RA needs.
|
||||
$CARGO "$@"
|
||||
;;
|
||||
*)
|
||||
echo "Unknown command: $COMMAND"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
# Instead of doing just `cargo run --manifest-path .. $@`, we invoke miri-script binary directly. Invoking `cargo run` goes through
|
||||
# rustup (that sets it's own environmental variables), which is undesirable.
|
||||
cargo build -q --manifest-path "$(dirname "$0")"/miri-script/Cargo.toml
|
||||
"$(dirname "$0")"/miri-script/target/debug/miri-script "$@"
|
||||
|
160
src/tools/miri/miri-script/Cargo.lock
Normal file
160
src/tools/miri/miri-script/Cargo.lock
Normal 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"
|
22
src/tools/miri/miri-script/Cargo.toml
Normal file
22
src/tools/miri/miri-script/Cargo.toml
Normal 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"
|
4
src/tools/miri/miri-script/miri
Executable file
4
src/tools/miri/miri-script/miri
Executable 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 "$@"
|
468
src/tools/miri/miri-script/src/commands.rs
Normal file
468
src/tools/miri/miri-script/src/commands.rs
Normal 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(())
|
||||
}
|
||||
}
|
210
src/tools/miri/miri-script/src/main.rs
Normal file
210
src/tools/miri/miri-script/src/main.rs
Normal 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(())
|
||||
}
|
144
src/tools/miri/miri-script/src/util.rs
Normal file
144
src/tools/miri/miri-script/src/util.rs
Normal 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(())
|
||||
}
|
||||
}
|
@ -1 +1 @@
|
||||
d150dbb067e66f351a0b33a54e7d4b464ef51e47
|
||||
fca59ab5f0e7df7d816bed77a32abc0045ebe80b
|
@ -13,11 +13,7 @@ use log::trace;
|
||||
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_middle::mir::{Mutability, RetagKind};
|
||||
use rustc_middle::ty::{
|
||||
self,
|
||||
layout::HasParamEnv,
|
||||
Ty,
|
||||
};
|
||||
use rustc_middle::ty::{self, layout::HasParamEnv, Ty};
|
||||
use rustc_target::abi::{Abi, Align, Size};
|
||||
|
||||
use crate::borrow_tracker::{
|
||||
@ -608,8 +604,7 @@ impl<'mir: 'ecx, 'tcx: 'mir, 'ecx> EvalContextPrivExt<'mir, 'tcx, 'ecx>
|
||||
{
|
||||
}
|
||||
trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'mir, 'tcx> {
|
||||
/// Returns the `AllocId` the reborrow was done in, if some actual borrow stack manipulation
|
||||
/// happened.
|
||||
/// Returns the provenance that should be used henceforth.
|
||||
fn sb_reborrow(
|
||||
&mut self,
|
||||
place: &MPlaceTy<'tcx, Provenance>,
|
||||
@ -617,7 +612,7 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
|
||||
new_perm: NewPermission,
|
||||
new_tag: BorTag,
|
||||
retag_info: RetagInfo, // diagnostics info about this retag
|
||||
) -> InterpResult<'tcx, Option<AllocId>> {
|
||||
) -> InterpResult<'tcx, Option<Provenance>> {
|
||||
let this = self.eval_context_mut();
|
||||
// Ensure we bail out if the pointer goes out-of-bounds (see miri#1050).
|
||||
this.check_ptr_access_align(place.ptr, size, Align::ONE, CheckInAllocMsg::InboundsTest)?;
|
||||
@ -699,11 +694,14 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
|
||||
// pointer tagging for example all calls to get_unchecked on them are invalid.
|
||||
if let Ok((alloc_id, base_offset, orig_tag)) = this.ptr_try_get_alloc_id(place.ptr) {
|
||||
log_creation(this, Some((alloc_id, base_offset, orig_tag)))?;
|
||||
return Ok(Some(alloc_id));
|
||||
// Still give it the new provenance, it got retagged after all.
|
||||
return Ok(Some(Provenance::Concrete { alloc_id, tag: new_tag }));
|
||||
} else {
|
||||
// This pointer doesn't come with an AllocId. :shrug:
|
||||
log_creation(this, None)?;
|
||||
// Provenance unchanged.
|
||||
return Ok(place.ptr.provenance);
|
||||
}
|
||||
// This pointer doesn't come with an AllocId. :shrug:
|
||||
log_creation(this, None)?;
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let (alloc_id, base_offset, orig_tag) = this.ptr_get_alloc_id(place.ptr)?;
|
||||
@ -808,7 +806,7 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(alloc_id))
|
||||
Ok(Some(Provenance::Concrete { alloc_id, tag: new_tag }))
|
||||
}
|
||||
|
||||
/// Retags an individual pointer, returning the retagged version.
|
||||
@ -835,25 +833,10 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
|
||||
let new_tag = this.machine.borrow_tracker.as_mut().unwrap().get_mut().new_ptr();
|
||||
|
||||
// Reborrow.
|
||||
let alloc_id = this.sb_reborrow(&place, size, new_perm, new_tag, info)?;
|
||||
let new_prov = this.sb_reborrow(&place, size, new_perm, new_tag, info)?;
|
||||
|
||||
// Adjust pointer.
|
||||
let new_place = place.map_provenance(|p| {
|
||||
p.map(|prov| {
|
||||
match alloc_id {
|
||||
Some(alloc_id) => {
|
||||
// If `reborrow` could figure out the AllocId of this ptr, hard-code it into the new one.
|
||||
// Even if we started out with a wildcard, this newly retagged pointer is tied to that allocation.
|
||||
Provenance::Concrete { alloc_id, tag: new_tag }
|
||||
}
|
||||
None => {
|
||||
// Looks like this has to stay a wildcard pointer.
|
||||
assert!(matches!(prov, Provenance::Wildcard));
|
||||
Provenance::Wildcard
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
let new_place = place.map_provenance(|_| new_prov);
|
||||
|
||||
// Return new pointer.
|
||||
Ok(ImmTy::from_immediate(new_place.to_ref(this), val.layout))
|
||||
|
@ -218,7 +218,7 @@ impl<'tcx> Tree {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(super) enum TransitionError {
|
||||
/// This access is not allowed because some parent tag has insufficient permissions.
|
||||
/// For example, if a tag is `Frozen` and encounters a child write this will
|
||||
|
@ -5,11 +5,7 @@ use rustc_target::abi::{Abi, Align, Size};
|
||||
use crate::borrow_tracker::{AccessKind, GlobalStateInner, ProtectorKind, RetagFields};
|
||||
use rustc_middle::{
|
||||
mir::{Mutability, RetagKind},
|
||||
ty::{
|
||||
self,
|
||||
layout::HasParamEnv,
|
||||
Ty,
|
||||
},
|
||||
ty::{self, layout::HasParamEnv, Ty},
|
||||
};
|
||||
use rustc_span::def_id::DefId;
|
||||
|
||||
@ -121,7 +117,7 @@ impl<'tcx> NewPermission {
|
||||
let ty_is_freeze = pointee.is_freeze(*cx.tcx, cx.param_env());
|
||||
let ty_is_unpin = pointee.is_unpin(*cx.tcx, cx.param_env());
|
||||
let initial_state = match mutability {
|
||||
Mutability::Mut if ty_is_unpin => Permission::new_unique_2phase(ty_is_freeze),
|
||||
Mutability::Mut if ty_is_unpin => Permission::new_reserved(ty_is_freeze),
|
||||
Mutability::Not if ty_is_freeze => Permission::new_frozen(),
|
||||
// Raw pointers never enter this function so they are not handled.
|
||||
// However raw pointers are not the only pointers that take the parent
|
||||
@ -150,7 +146,7 @@ impl<'tcx> NewPermission {
|
||||
let ty_is_freeze = ty.is_freeze(*cx.tcx, cx.param_env());
|
||||
Self {
|
||||
zero_size,
|
||||
initial_state: Permission::new_unique_2phase(ty_is_freeze),
|
||||
initial_state: Permission::new_reserved(ty_is_freeze),
|
||||
protector: (kind == RetagKind::FnEntry).then_some(ProtectorKind::WeakProtector),
|
||||
}
|
||||
})
|
||||
@ -165,25 +161,22 @@ impl<'mir: 'ecx, 'tcx: 'mir, 'ecx> EvalContextPrivExt<'mir, 'tcx, 'ecx>
|
||||
{
|
||||
}
|
||||
trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'mir, 'tcx> {
|
||||
/// Returns the `AllocId` the reborrow was done in, if there is some actual
|
||||
/// memory associated with this pointer. Returns `None` if there is no actual
|
||||
/// memory allocated. Also checks that the reborrow of size `ptr_size` is
|
||||
/// within bounds of the allocation.
|
||||
///
|
||||
/// Also returns the tag that the pointer should get, which is essentially
|
||||
/// `if new_perm.is_some() { new_tag } else { parent_tag }` along with
|
||||
/// some logging (always) and fake reads (if `new_perm` is
|
||||
/// `Some(NewPermission { perform_read_access: true }`).
|
||||
/// Returns the provenance that should be used henceforth.
|
||||
fn tb_reborrow(
|
||||
&mut self,
|
||||
place: &MPlaceTy<'tcx, Provenance>, // parent tag extracted from here
|
||||
ptr_size: Size,
|
||||
new_perm: NewPermission,
|
||||
new_tag: BorTag,
|
||||
) -> InterpResult<'tcx, Option<(AllocId, BorTag)>> {
|
||||
) -> InterpResult<'tcx, Option<Provenance>> {
|
||||
let this = self.eval_context_mut();
|
||||
// Ensure we bail out if the pointer goes out-of-bounds (see miri#1050).
|
||||
this.check_ptr_access_align(place.ptr, ptr_size, Align::ONE, CheckInAllocMsg::InboundsTest)?;
|
||||
this.check_ptr_access_align(
|
||||
place.ptr,
|
||||
ptr_size,
|
||||
Align::ONE,
|
||||
CheckInAllocMsg::InboundsTest,
|
||||
)?;
|
||||
|
||||
// It is crucial that this gets called on all code paths, to ensure we track tag creation.
|
||||
let log_creation = |this: &MiriInterpCx<'mir, 'tcx>,
|
||||
@ -209,7 +202,7 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
|
||||
// Unlike SB, we *do* a proper retag for size 0 if can identify the allocation.
|
||||
// After all, the pointer may be lazily initialized outside this initial range.
|
||||
data
|
||||
},
|
||||
}
|
||||
Err(_) => {
|
||||
assert_eq!(ptr_size, Size::ZERO); // we did the deref check above, size has to be 0 here
|
||||
// This pointer doesn't come with an AllocId, so there's no
|
||||
@ -221,13 +214,14 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
|
||||
place.layout.ty,
|
||||
);
|
||||
log_creation(this, None)?;
|
||||
return Ok(None);
|
||||
// Keep original provenance.
|
||||
return Ok(place.ptr.provenance);
|
||||
}
|
||||
};
|
||||
log_creation(this, Some((alloc_id, base_offset, parent_prov)))?;
|
||||
|
||||
let orig_tag = match parent_prov {
|
||||
ProvenanceExtra::Wildcard => return Ok(None), // TODO: handle wildcard pointers
|
||||
ProvenanceExtra::Wildcard => return Ok(place.ptr.provenance), // TODO: handle wildcard pointers
|
||||
ProvenanceExtra::Concrete(tag) => tag,
|
||||
};
|
||||
|
||||
@ -254,31 +248,54 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
|
||||
.insert(new_tag, protect);
|
||||
}
|
||||
|
||||
let alloc_kind = this.get_alloc_info(alloc_id).2;
|
||||
if !matches!(alloc_kind, AllocKind::LiveData) {
|
||||
assert_eq!(ptr_size, Size::ZERO); // we did the deref check above, size has to be 0 here
|
||||
// There's not actually any bytes here where accesses could even be tracked.
|
||||
// Just produce the new provenance, nothing else to do.
|
||||
return Ok(Some(Provenance::Concrete { alloc_id, tag: new_tag }));
|
||||
}
|
||||
|
||||
let span = this.machine.current_span();
|
||||
let alloc_extra = this.get_alloc_extra(alloc_id)?;
|
||||
let range = alloc_range(base_offset, ptr_size);
|
||||
let mut tree_borrows = alloc_extra.borrow_tracker_tb().borrow_mut();
|
||||
|
||||
// All reborrows incur a (possibly zero-sized) read access to the parent
|
||||
{
|
||||
let global = &this.machine.borrow_tracker.as_ref().unwrap();
|
||||
let span = this.machine.current_span();
|
||||
tree_borrows.perform_access(
|
||||
AccessKind::Read,
|
||||
orig_tag,
|
||||
range,
|
||||
global,
|
||||
span,
|
||||
diagnostics::AccessCause::Reborrow,
|
||||
)?;
|
||||
tree_borrows.perform_access(
|
||||
AccessKind::Read,
|
||||
orig_tag,
|
||||
range,
|
||||
this.machine.borrow_tracker.as_ref().unwrap(),
|
||||
this.machine.current_span(),
|
||||
diagnostics::AccessCause::Reborrow,
|
||||
)?;
|
||||
// Record the parent-child pair in the tree.
|
||||
tree_borrows.new_child(orig_tag, new_tag, new_perm.initial_state, range, span)?;
|
||||
drop(tree_borrows);
|
||||
|
||||
// Also inform the data race model (but only if any bytes are actually affected).
|
||||
if range.size.bytes() > 0 {
|
||||
if let Some(data_race) = alloc_extra.data_race.as_ref() {
|
||||
data_race.read(alloc_id, range, &this.machine)?;
|
||||
// We sometimes need to make it a write, since not all retags commute with reads!
|
||||
// FIXME: Is that truly the semantics we want? Some optimizations are likely to be
|
||||
// very unhappy without this. We'd tsill ge some UB just by picking a suitable
|
||||
// interleaving, but wether UB happens can depend on whether a write occurs in the
|
||||
// future...
|
||||
let is_write = new_perm.initial_state.is_active()
|
||||
|| (new_perm.initial_state.is_resrved() && new_perm.protector.is_some());
|
||||
if is_write {
|
||||
// Need to get mutable access to alloc_extra.
|
||||
// (Cannot always do this as we can do read-only reborrowing on read-only allocations.)
|
||||
let (alloc_extra, machine) = this.get_alloc_extra_mut(alloc_id)?;
|
||||
alloc_extra.data_race.as_mut().unwrap().write(alloc_id, range, machine)?;
|
||||
} else {
|
||||
data_race.read(alloc_id, range, &this.machine)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Record the parent-child pair in the tree.
|
||||
tree_borrows.new_child(orig_tag, new_tag, new_perm.initial_state, range, span)?;
|
||||
Ok(Some((alloc_id, new_tag)))
|
||||
Ok(Some(Provenance::Concrete { alloc_id, tag: new_tag }))
|
||||
}
|
||||
|
||||
/// Retags an individual pointer, returning the retagged version.
|
||||
@ -314,25 +331,10 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
|
||||
let new_tag = this.machine.borrow_tracker.as_mut().unwrap().get_mut().new_ptr();
|
||||
|
||||
// Compute the actual reborrow.
|
||||
let reborrowed = this.tb_reborrow(&place, reborrow_size, new_perm, new_tag)?;
|
||||
let new_prov = this.tb_reborrow(&place, reborrow_size, new_perm, new_tag)?;
|
||||
|
||||
// Adjust pointer.
|
||||
let new_place = place.map_provenance(|p| {
|
||||
p.map(|prov| {
|
||||
match reborrowed {
|
||||
Some((alloc_id, actual_tag)) => {
|
||||
// If `reborrow` could figure out the AllocId of this ptr, hard-code it into the new one.
|
||||
// Even if we started out with a wildcard, this newly retagged pointer is tied to that allocation.
|
||||
Provenance::Concrete { alloc_id, tag: actual_tag }
|
||||
}
|
||||
None => {
|
||||
// Looks like this has to stay a wildcard pointer.
|
||||
assert!(matches!(prov, Provenance::Wildcard));
|
||||
Provenance::Wildcard
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
let new_place = place.map_provenance(|_| new_prov);
|
||||
|
||||
// Return new pointer.
|
||||
Ok(ImmTy::from_immediate(new_place.to_ref(this), val.layout))
|
||||
|
@ -134,25 +134,32 @@ pub struct PermTransition {
|
||||
|
||||
impl Permission {
|
||||
/// Default initial permission of the root of a new tree.
|
||||
pub fn new_root() -> Self {
|
||||
pub fn new_active() -> Self {
|
||||
Self { inner: Active }
|
||||
}
|
||||
|
||||
/// Default initial permission of a reborrowed mutable reference.
|
||||
pub fn new_unique_2phase(ty_is_freeze: bool) -> Self {
|
||||
pub fn new_reserved(ty_is_freeze: bool) -> Self {
|
||||
Self { inner: Reserved { ty_is_freeze } }
|
||||
}
|
||||
|
||||
/// Default initial permission for return place.
|
||||
pub fn new_active() -> Self {
|
||||
Self { inner: Active }
|
||||
}
|
||||
|
||||
/// Default initial permission of a reborrowed shared reference
|
||||
pub fn new_frozen() -> Self {
|
||||
Self { inner: Frozen }
|
||||
}
|
||||
|
||||
pub fn is_active(self) -> bool {
|
||||
matches!(self.inner, Active)
|
||||
}
|
||||
|
||||
pub fn is_resrved(self) -> bool {
|
||||
matches!(self.inner, Reserved { .. })
|
||||
}
|
||||
|
||||
pub fn is_frozen(self) -> bool {
|
||||
matches!(self.inner, Frozen)
|
||||
}
|
||||
|
||||
/// Apply the transition to the inner PermissionPriv.
|
||||
pub fn perform_access(
|
||||
kind: AccessKind,
|
||||
@ -438,7 +445,7 @@ mod propagation_optimization_checks {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn foreign_read_is_noop_after_write() {
|
||||
fn foreign_read_is_noop_after_foreign_write() {
|
||||
use transition::*;
|
||||
let old_access = AccessKind::Write;
|
||||
let new_access = AccessKind::Read;
|
||||
|
@ -110,7 +110,7 @@ impl LocationState {
|
||||
|
||||
// Helper to optimize the tree traversal.
|
||||
// The optimization here consists of observing thanks to the tests
|
||||
// `foreign_read_is_noop_after_write` and `all_transitions_idempotent`,
|
||||
// `foreign_read_is_noop_after_foreign_write` and `all_transitions_idempotent`,
|
||||
// that there are actually just three possible sequences of events that can occur
|
||||
// in between two child accesses that produce different results.
|
||||
//
|
||||
@ -139,7 +139,7 @@ impl LocationState {
|
||||
let new_access_noop = match (self.latest_foreign_access, access_kind) {
|
||||
// Previously applied transition makes the new one a guaranteed
|
||||
// noop in the two following cases:
|
||||
// (1) justified by `foreign_read_is_noop_after_write`
|
||||
// (1) justified by `foreign_read_is_noop_after_foreign_write`
|
||||
(Some(AccessKind::Write), AccessKind::Read) => true,
|
||||
// (2) justified by `all_transitions_idempotent`
|
||||
(Some(old), new) if old == new => true,
|
||||
@ -376,7 +376,7 @@ where {
|
||||
impl Tree {
|
||||
/// Create a new tree, with only a root pointer.
|
||||
pub fn new(root_tag: BorTag, size: Size, span: Span) -> Self {
|
||||
let root_perm = Permission::new_root();
|
||||
let root_perm = Permission::new_active();
|
||||
let mut tag_mapping = UniKeyMap::default();
|
||||
let root_idx = tag_mapping.insert(root_tag);
|
||||
let nodes = {
|
||||
@ -670,7 +670,8 @@ impl AccessRelatedness {
|
||||
mod commutation_tests {
|
||||
use super::*;
|
||||
impl LocationState {
|
||||
pub fn all_without_access() -> impl Iterator<Item = Self> {
|
||||
pub fn all() -> impl Iterator<Item = Self> {
|
||||
// We keep `latest_foreign_access` at `None` as that's just a cache.
|
||||
Permission::all().flat_map(|permission| {
|
||||
[false, true].into_iter().map(move |initialized| {
|
||||
Self { permission, initialized, latest_foreign_access: None }
|
||||
@ -695,12 +696,12 @@ mod commutation_tests {
|
||||
// Any protector state works, but we can't move reads across function boundaries
|
||||
// so the two read accesses occur under the same protector.
|
||||
for &protected in &[true, false] {
|
||||
for loc in LocationState::all_without_access() {
|
||||
for loc in LocationState::all() {
|
||||
// Apply 1 then 2. Failure here means that there is UB in the source
|
||||
// and we skip the check in the target.
|
||||
let mut loc12 = loc;
|
||||
let Ok(_) = loc12.perform_access(kind, rel1, protected) else { continue; };
|
||||
let Ok(_) = loc12.perform_access(kind, rel2, protected) else { continue; };
|
||||
let Ok(_) = loc12.perform_access(kind, rel1, protected) else { continue };
|
||||
let Ok(_) = loc12.perform_access(kind, rel2, protected) else { continue };
|
||||
|
||||
// If 1 followed by 2 succeeded, then 2 followed by 1 must also succeed...
|
||||
let mut loc21 = loc;
|
||||
@ -718,4 +719,33 @@ mod commutation_tests {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[rustfmt::skip]
|
||||
// Ensure that of 2 accesses happen, one foreign and one a child, and we are protected, that we
|
||||
// get UB unless they are both reads.
|
||||
fn protected_enforces_noalias() {
|
||||
for rel1 in AccessRelatedness::all() {
|
||||
for rel2 in AccessRelatedness::all() {
|
||||
if rel1.is_foreign() == rel2.is_foreign() {
|
||||
// We want to check pairs of accesses where one is foreign and one is not.
|
||||
continue;
|
||||
}
|
||||
for kind1 in AccessKind::all() {
|
||||
for kind2 in AccessKind::all() {
|
||||
for mut state in LocationState::all() {
|
||||
let protected = true;
|
||||
let Ok(_) = state.perform_access(kind1, rel1, protected) else { continue };
|
||||
let Ok(_) = state.perform_access(kind2, rel2, protected) else { continue };
|
||||
// If these were both allowed, it must have been two reads.
|
||||
assert!(
|
||||
kind1 == AccessKind::Read && kind2 == AccessKind::Read,
|
||||
"failed to enforce noalias between two accesses that are not both reads"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -337,7 +337,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
|
||||
/// Call a function: Push the stack frame and pass the arguments.
|
||||
/// For now, arguments must be scalars (so that the caller does not have to know the layout).
|
||||
///
|
||||
/// If you do not provie a return place, a dangling zero-sized place will be created
|
||||
/// If you do not provide a return place, a dangling zero-sized place will be created
|
||||
/// for your convenience.
|
||||
fn call_function(
|
||||
&mut self,
|
||||
|
@ -427,7 +427,7 @@ pub struct MiriMachine<'mir, 'tcx> {
|
||||
/// the emulated program.
|
||||
profiler: Option<measureme::Profiler>,
|
||||
/// Used with `profiler` to cache the `StringId`s for event names
|
||||
/// uesd with `measureme`.
|
||||
/// used with `measureme`.
|
||||
string_cache: FxHashMap<String, measureme::StringId>,
|
||||
|
||||
/// Cache of `Instance` exported under the given `Symbol` name.
|
||||
@ -516,7 +516,7 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> {
|
||||
let pid = process::id();
|
||||
// We adopt the same naming scheme for the profiler output that rustc uses. In rustc,
|
||||
// the PID is padded so that the nondeterministic value of the PID does not spread
|
||||
// nondeterminisim to the allocator. In Miri we are not aiming for such performance
|
||||
// nondeterminism to the allocator. In Miri we are not aiming for such performance
|
||||
// control, we just pad for consistency with rustc.
|
||||
let filename = format!("{crate_name}-{pid:07}");
|
||||
let path = Path::new(out).join(filename);
|
||||
@ -1219,7 +1219,8 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
|
||||
// If we have a borrow tracker, we also have it set up protection so that all reads *and
|
||||
// writes* during this call are insta-UB.
|
||||
if ecx.machine.borrow_tracker.is_some() {
|
||||
if let Either::Left(place) = place.as_mplace_or_local() {
|
||||
// Have to do `to_op` first because a `Place::Local` doesn't imply the local doesn't have an address.
|
||||
if let Either::Left(place) = ecx.place_to_op(place)?.as_mplace_or_imm() {
|
||||
ecx.protect_place(&place)?;
|
||||
} else {
|
||||
// Locals that don't have their address taken are as protected as they can ever be.
|
||||
|
@ -73,11 +73,11 @@ fn test_config(target: &str, path: &str, mode: Mode, with_dependencies: bool) ->
|
||||
program.args.push(flag);
|
||||
}
|
||||
|
||||
let bless = env::var_os("RUSTC_BLESS").is_some_and(|v| v !="0");
|
||||
let bless = env::var_os("RUSTC_BLESS").is_some_and(|v| v != "0");
|
||||
let skip_ui_checks = env::var_os("MIRI_SKIP_UI_CHECKS").is_some();
|
||||
|
||||
let output_conflict_handling = match (bless, skip_ui_checks) {
|
||||
(false, false) => OutputConflictHandling::Error("./miri bless".into()),
|
||||
(false, false) => OutputConflictHandling::Error("./miri test --bless".into()),
|
||||
(true, false) => OutputConflictHandling::Bless,
|
||||
(false, true) => OutputConflictHandling::Ignore,
|
||||
(true, true) => panic!("cannot use RUSTC_BLESS and MIRI_SKIP_UI_CHECKS at the same time"),
|
||||
|
@ -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();
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -1,5 +1,7 @@
|
||||
//! Make sure that a retag acts like a write for the data race model.
|
||||
//@revisions: stack tree
|
||||
//@compile-flags: -Zmiri-preemption-rate=0
|
||||
//@[tree]compile-flags: -Zmiri-tree-borrows
|
||||
#[derive(Copy, Clone)]
|
||||
struct SendPtr(*mut u8);
|
||||
|
||||
@ -15,7 +17,7 @@ fn thread_1(p: SendPtr) {
|
||||
fn thread_2(p: SendPtr) {
|
||||
let p = p.0;
|
||||
unsafe {
|
||||
*p = 5; //~ ERROR: Data race detected between (1) Write on thread `<unnamed>` and (2) Write on thread `<unnamed>`
|
||||
*p = 5; //~ ERROR: /Data race detected between \(1\) (Read|Write) on thread `<unnamed>` and \(2\) Write on thread `<unnamed>`/
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
@ -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
|
||||
|
@ -1,23 +1,23 @@
|
||||
error: Undefined Behavior: Data race detected between (1) Read on thread `<unnamed>` and (2) Write on thread `<unnamed>` at ALLOC. (2) just happened here
|
||||
--> $DIR/retag-data-race.rs:LL:CC
|
||||
--> $DIR/retag_data_race_read.rs:LL:CC
|
||||
|
|
||||
LL | *p = 5;
|
||||
| ^^^^^^ Data race detected between (1) Read on thread `<unnamed>` and (2) Write on thread `<unnamed>` at ALLOC. (2) just happened here
|
||||
LL | *p = 5;
|
||||
| ^^^^^^ Data race detected between (1) Read on thread `<unnamed>` and (2) Write on thread `<unnamed>` at ALLOC. (2) just happened here
|
||||
|
|
||||
help: and (1) occurred earlier here
|
||||
--> $DIR/retag-data-race.rs:LL:CC
|
||||
--> $DIR/retag_data_race_read.rs:LL:CC
|
||||
|
|
||||
LL | let _r = &*p;
|
||||
| ^^^
|
||||
LL | let _r = &*p;
|
||||
| ^^^
|
||||
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
|
||||
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
|
||||
= note: BACKTRACE (of the first span):
|
||||
= note: inside `thread_2` at $DIR/retag-data-race.rs:LL:CC
|
||||
= note: inside `thread_2` at $DIR/retag_data_race_read.rs:LL:CC
|
||||
note: inside closure
|
||||
--> $DIR/retag-data-race.rs:LL:CC
|
||||
--> $DIR/retag_data_race_read.rs:LL:CC
|
||||
|
|
||||
LL | let t2 = std::thread::spawn(move || unsafe { thread_2(p) });
|
||||
| ^^^^^^^^^^^
|
||||
LL | let t2 = std::thread::spawn(move || thread_2(p));
|
||||
| ^^^^^^^^^^^
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
@ -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
|
||||
|
@ -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/
|
||||
}
|
@ -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
|
||||
|
@ -3,8 +3,8 @@
|
||||
// Check how a Reserved with interior mutability
|
||||
// responds to a Foreign Write under a Protector
|
||||
#[path = "../../../utils/mod.rs"]
|
||||
#[macro_use]
|
||||
mod utils;
|
||||
use utils::macros::*;
|
||||
|
||||
use std::cell::UnsafeCell;
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-tag-gc=0
|
||||
|
||||
#[path = "../../../utils/mod.rs"]
|
||||
#[macro_use]
|
||||
mod utils;
|
||||
use utils::macros::*;
|
||||
|
||||
// Check how a Reserved without interior mutability responds to a Foreign
|
||||
// Write when under a protector
|
||||
|
@ -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();
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -5,12 +5,15 @@
|
||||
#![feature(io_error_uncategorized)]
|
||||
|
||||
use std::convert::TryInto;
|
||||
use std::ffi::{c_char, CStr, CString};
|
||||
use std::ffi::CString;
|
||||
use std::fs::{canonicalize, remove_dir_all, remove_file, File};
|
||||
use std::io::{Error, ErrorKind, Write};
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[path = "../../utils/mod.rs"]
|
||||
mod utils;
|
||||
|
||||
fn main() {
|
||||
test_dup_stdout_stderr();
|
||||
test_canonicalize_too_long();
|
||||
@ -22,31 +25,9 @@ fn main() {
|
||||
test_o_tmpfile_flag();
|
||||
}
|
||||
|
||||
fn tmp() -> PathBuf {
|
||||
let path = std::env::var("MIRI_TEMP")
|
||||
.unwrap_or_else(|_| std::env::temp_dir().into_os_string().into_string().unwrap());
|
||||
// These are host paths. We need to convert them to the target.
|
||||
let path = CString::new(path).unwrap();
|
||||
let mut out = Vec::with_capacity(1024);
|
||||
|
||||
unsafe {
|
||||
extern "Rust" {
|
||||
fn miri_host_to_target_path(
|
||||
path: *const c_char,
|
||||
out: *mut c_char,
|
||||
out_size: usize,
|
||||
) -> usize;
|
||||
}
|
||||
let ret = miri_host_to_target_path(path.as_ptr(), out.as_mut_ptr(), out.capacity());
|
||||
assert_eq!(ret, 0);
|
||||
let out = CStr::from_ptr(out.as_ptr()).to_str().unwrap();
|
||||
PathBuf::from(out)
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepare: compute filename and make sure the file does not exist.
|
||||
fn prepare(filename: &str) -> PathBuf {
|
||||
let path = tmp().join(filename);
|
||||
let path = utils::tmp().join(filename);
|
||||
// Clean the paths for robustness.
|
||||
remove_file(&path).ok();
|
||||
path
|
||||
@ -55,7 +36,7 @@ fn prepare(filename: &str) -> PathBuf {
|
||||
/// Prepare directory: compute directory name and make sure it does not exist.
|
||||
#[allow(unused)]
|
||||
fn prepare_dir(dirname: &str) -> PathBuf {
|
||||
let path = tmp().join(&dirname);
|
||||
let path = utils::tmp().join(&dirname);
|
||||
// Clean the directory for robustness.
|
||||
remove_dir_all(&path).ok();
|
||||
path
|
||||
|
@ -6,29 +6,8 @@ use std::fs::{remove_file, File};
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn tmp() -> PathBuf {
|
||||
use std::ffi::{c_char, CStr, CString};
|
||||
|
||||
let path = std::env::var("MIRI_TEMP")
|
||||
.unwrap_or_else(|_| std::env::temp_dir().into_os_string().into_string().unwrap());
|
||||
// These are host paths. We need to convert them to the target.
|
||||
let path = CString::new(path).unwrap();
|
||||
let mut out = Vec::with_capacity(1024);
|
||||
|
||||
unsafe {
|
||||
extern "Rust" {
|
||||
fn miri_host_to_target_path(
|
||||
path: *const c_char,
|
||||
out: *mut c_char,
|
||||
out_size: usize,
|
||||
) -> usize;
|
||||
}
|
||||
let ret = miri_host_to_target_path(path.as_ptr(), out.as_mut_ptr(), out.capacity());
|
||||
assert_eq!(ret, 0);
|
||||
let out = CStr::from_ptr(out.as_ptr()).to_str().unwrap();
|
||||
PathBuf::from(out)
|
||||
}
|
||||
}
|
||||
#[path = "../../utils/mod.rs"]
|
||||
mod utils;
|
||||
|
||||
/// Test allocating variant of `realpath`.
|
||||
fn test_posix_realpath_alloc() {
|
||||
@ -38,7 +17,7 @@ fn test_posix_realpath_alloc() {
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
|
||||
let buf;
|
||||
let path = tmp().join("miri_test_libc_posix_realpath_alloc");
|
||||
let path = utils::tmp().join("miri_test_libc_posix_realpath_alloc");
|
||||
let c_path = CString::new(path.as_os_str().as_bytes()).expect("CString::new failed");
|
||||
|
||||
// Cleanup before test.
|
||||
@ -63,7 +42,7 @@ fn test_posix_realpath_noalloc() {
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
let path = tmp().join("miri_test_libc_posix_realpath_noalloc");
|
||||
let path = utils::tmp().join("miri_test_libc_posix_realpath_noalloc");
|
||||
let c_path = CString::new(path.as_os_str().as_bytes()).expect("CString::new failed");
|
||||
|
||||
let mut v = vec![0; libc::PATH_MAX as usize];
|
||||
@ -103,7 +82,7 @@ fn test_posix_realpath_errors() {
|
||||
fn test_posix_fadvise() {
|
||||
use std::io::Write;
|
||||
|
||||
let path = tmp().join("miri_test_libc_posix_fadvise.txt");
|
||||
let path = utils::tmp().join("miri_test_libc_posix_fadvise.txt");
|
||||
// Cleanup before test
|
||||
remove_file(&path).ok();
|
||||
|
||||
@ -130,7 +109,7 @@ fn test_posix_fadvise() {
|
||||
fn test_sync_file_range() {
|
||||
use std::io::Write;
|
||||
|
||||
let path = tmp().join("miri_test_libc_sync_file_range.txt");
|
||||
let path = utils::tmp().join("miri_test_libc_sync_file_range.txt");
|
||||
// Cleanup before test.
|
||||
remove_file(&path).ok();
|
||||
|
||||
@ -243,7 +222,7 @@ fn test_isatty() {
|
||||
libc::isatty(libc::STDERR_FILENO);
|
||||
|
||||
// But when we open a file, it is definitely not a TTY.
|
||||
let path = tmp().join("notatty.txt");
|
||||
let path = utils::tmp().join("notatty.txt");
|
||||
// Cleanup before test.
|
||||
remove_file(&path).ok();
|
||||
let file = File::create(&path).unwrap();
|
||||
|
@ -2,17 +2,21 @@
|
||||
use std::intrinsics::mir::*;
|
||||
|
||||
#[repr(packed)]
|
||||
struct S { field: [u32; 2] }
|
||||
struct S {
|
||||
field: [u32; 2],
|
||||
}
|
||||
|
||||
#[custom_mir(dialect = "runtime", phase = "optimized")]
|
||||
fn test() { mir! {
|
||||
let s: S;
|
||||
{
|
||||
// Store a repeat expression directly into a field of a packed struct.
|
||||
s.field = [0; 2];
|
||||
Return()
|
||||
fn test() {
|
||||
mir! {
|
||||
let s: S;
|
||||
{
|
||||
// Store a repeat expression directly into a field of a packed struct.
|
||||
s.field = [0; 2];
|
||||
Return()
|
||||
}
|
||||
}
|
||||
} }
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Run this a bunch of time to make sure it doesn't pass by chance.
|
||||
|
@ -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());
|
||||
}
|
@ -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)>;
|
||||
}
|
@ -20,6 +20,15 @@ fn basic_raw() {
|
||||
assert_eq!(*x, 23);
|
||||
}
|
||||
|
||||
fn assign_overlapping() {
|
||||
// Test an assignment where LHS and RHS alias.
|
||||
// In Mir, that's UB (see `fail/overlapping_assignment.rs`), but in surface Rust this is allowed.
|
||||
let mut mem = [0u32; 4];
|
||||
let ptr = &mut mem as *mut [u32; 4];
|
||||
unsafe { *ptr = *ptr };
|
||||
}
|
||||
|
||||
fn main() {
|
||||
basic_raw();
|
||||
assign_overlapping();
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
#![feature(io_error_uncategorized)]
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::{c_char, OsString};
|
||||
use std::ffi::OsString;
|
||||
use std::fs::{
|
||||
canonicalize, create_dir, read_dir, read_link, remove_dir, remove_dir_all, remove_file, rename,
|
||||
File, OpenOptions,
|
||||
@ -13,6 +13,9 @@ use std::fs::{
|
||||
use std::io::{Error, ErrorKind, IsTerminal, Read, Result, Seek, SeekFrom, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[path = "../../utils/mod.rs"]
|
||||
mod utils;
|
||||
|
||||
fn main() {
|
||||
test_path_conversion();
|
||||
test_file();
|
||||
@ -30,37 +33,9 @@ fn main() {
|
||||
test_from_raw_os_error();
|
||||
}
|
||||
|
||||
fn host_to_target_path(path: String) -> PathBuf {
|
||||
use std::ffi::{CStr, CString};
|
||||
|
||||
let path = CString::new(path).unwrap();
|
||||
let mut out = Vec::with_capacity(1024);
|
||||
|
||||
unsafe {
|
||||
extern "Rust" {
|
||||
fn miri_host_to_target_path(
|
||||
path: *const c_char,
|
||||
out: *mut c_char,
|
||||
out_size: usize,
|
||||
) -> usize;
|
||||
}
|
||||
let ret = miri_host_to_target_path(path.as_ptr(), out.as_mut_ptr(), out.capacity());
|
||||
assert_eq!(ret, 0);
|
||||
let out = CStr::from_ptr(out.as_ptr()).to_str().unwrap();
|
||||
PathBuf::from(out)
|
||||
}
|
||||
}
|
||||
|
||||
fn tmp() -> PathBuf {
|
||||
let path = std::env::var("MIRI_TEMP")
|
||||
.unwrap_or_else(|_| std::env::temp_dir().into_os_string().into_string().unwrap());
|
||||
// These are host paths. We need to convert them to the target.
|
||||
host_to_target_path(path)
|
||||
}
|
||||
|
||||
/// Prepare: compute filename and make sure the file does not exist.
|
||||
fn prepare(filename: &str) -> PathBuf {
|
||||
let path = tmp().join(filename);
|
||||
let path = utils::tmp().join(filename);
|
||||
// Clean the paths for robustness.
|
||||
remove_file(&path).ok();
|
||||
path
|
||||
@ -68,7 +43,7 @@ fn prepare(filename: &str) -> PathBuf {
|
||||
|
||||
/// Prepare directory: compute directory name and make sure it does not exist.
|
||||
fn prepare_dir(dirname: &str) -> PathBuf {
|
||||
let path = tmp().join(&dirname);
|
||||
let path = utils::tmp().join(&dirname);
|
||||
// Clean the directory for robustness.
|
||||
remove_dir_all(&path).ok();
|
||||
path
|
||||
@ -83,7 +58,7 @@ fn prepare_with_content(filename: &str, content: &[u8]) -> PathBuf {
|
||||
}
|
||||
|
||||
fn test_path_conversion() {
|
||||
let tmp = tmp();
|
||||
let tmp = utils::tmp();
|
||||
assert!(tmp.is_absolute(), "{:?} is not absolute", tmp);
|
||||
assert!(tmp.is_dir(), "{:?} is not a directory", tmp);
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ fn main() {
|
||||
mut_raw_mut();
|
||||
partially_invalidate_mut();
|
||||
drop_after_sharing();
|
||||
direct_mut_to_const_raw();
|
||||
// direct_mut_to_const_raw();
|
||||
two_raw();
|
||||
shr_and_raw();
|
||||
disjoint_mutable_subborrows();
|
||||
@ -19,6 +19,7 @@ fn main() {
|
||||
mut_below_shr();
|
||||
wide_raw_ptr_in_tuple();
|
||||
not_unpin_not_protected();
|
||||
write_does_not_invalidate_all_aliases();
|
||||
}
|
||||
|
||||
// Make sure that reading from an `&mut` does, like reborrowing to `&`,
|
||||
@ -110,14 +111,13 @@ fn drop_after_sharing() {
|
||||
}
|
||||
|
||||
// Make sure that coercing &mut T to *const T produces a writeable pointer.
|
||||
fn direct_mut_to_const_raw() {
|
||||
// TODO: This is currently disabled, waiting on a decision on <https://github.com/rust-lang/rust/issues/56604>
|
||||
/*let x = &mut 0;
|
||||
// TODO: This is currently disabled, waiting on a decision on <https://github.com/rust-lang/rust/issues/56604>
|
||||
/*fn direct_mut_to_const_raw() {
|
||||
let x = &mut 0;
|
||||
let y: *const i32 = x;
|
||||
unsafe { *(y as *mut i32) = 1; }
|
||||
assert_eq!(*x, 1);
|
||||
*/
|
||||
}
|
||||
}*/
|
||||
|
||||
// Make sure that we can create two raw pointers from a mutable reference and use them both.
|
||||
fn two_raw() {
|
||||
@ -238,3 +238,28 @@ fn not_unpin_not_protected() {
|
||||
drop(unsafe { Box::from_raw(raw) });
|
||||
});
|
||||
}
|
||||
|
||||
fn write_does_not_invalidate_all_aliases() {
|
||||
mod other {
|
||||
/// Some private memory to store stuff in.
|
||||
static mut S: *mut i32 = 0 as *mut i32;
|
||||
|
||||
pub fn lib1(x: &&mut i32) {
|
||||
unsafe {
|
||||
S = (x as *const &mut i32).cast::<*mut i32>().read();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lib2() {
|
||||
unsafe {
|
||||
*S = 1337;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let x = &mut 0;
|
||||
other::lib1(&x);
|
||||
*x = 42; // a write to x -- invalidates other pointers?
|
||||
other::lib2();
|
||||
assert_eq!(*x, 1337); // oops, the value changed! I guess not all pointers were invalidated
|
||||
}
|
||||
|
25
src/tools/miri/tests/pass/strange_references.rs
Normal file
25
src/tools/miri/tests/pass/strange_references.rs
Normal 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
|
||||
})
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-tag-gc=0
|
||||
#[path = "../../utils/mod.rs"]
|
||||
#[macro_use]
|
||||
mod utils;
|
||||
use utils::macros::*;
|
||||
|
||||
use std::cell::UnsafeCell;
|
||||
|
||||
|
@ -3,8 +3,8 @@
|
||||
// Check that a protector goes back to normal behavior when the function
|
||||
// returns.
|
||||
#[path = "../../utils/mod.rs"]
|
||||
#[macro_use]
|
||||
mod utils;
|
||||
use utils::macros::*;
|
||||
|
||||
fn main() {
|
||||
unsafe {
|
||||
|
@ -1,8 +1,8 @@
|
||||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-tag-gc=0
|
||||
|
||||
#[path = "../../utils/mod.rs"]
|
||||
#[macro_use]
|
||||
mod utils;
|
||||
use utils::macros::*;
|
||||
|
||||
// Check the formatting of the trees.
|
||||
fn main() {
|
||||
|
@ -1,8 +1,8 @@
|
||||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-tag-gc=0
|
||||
|
||||
#[path = "../../utils/mod.rs"]
|
||||
#[macro_use]
|
||||
mod utils;
|
||||
use utils::macros::*;
|
||||
|
||||
// To check that a reborrow is counted as a Read access, we use a reborrow
|
||||
// with no additional Read to Freeze an Active pointer.
|
||||
|
@ -1,9 +1,8 @@
|
||||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-tag-gc=0
|
||||
|
||||
#[path = "../../utils/mod.rs"]
|
||||
#[macro_use]
|
||||
mod utils;
|
||||
use utils::macros::*;
|
||||
use utils::miri_extern::miri_write_to_stderr;
|
||||
|
||||
use std::cell::UnsafeCell;
|
||||
|
||||
@ -28,8 +27,8 @@ fn main() {
|
||||
}
|
||||
|
||||
unsafe fn print(msg: &str) {
|
||||
miri_write_to_stderr(msg.as_bytes());
|
||||
miri_write_to_stderr("\n".as_bytes());
|
||||
utils::miri_write_to_stderr(msg.as_bytes());
|
||||
utils::miri_write_to_stderr("\n".as_bytes());
|
||||
}
|
||||
|
||||
unsafe fn read_second<T>(x: &mut T, y: *mut u8) {
|
||||
|
@ -10,6 +10,8 @@ fn main() {
|
||||
aliasing_read_only_mutable_refs();
|
||||
string_as_mut_ptr();
|
||||
two_mut_protected_same_alloc();
|
||||
direct_mut_to_const_raw();
|
||||
local_addr_of_mut();
|
||||
|
||||
// Stacked Borrows tests
|
||||
read_does_not_invalidate1();
|
||||
@ -19,7 +21,6 @@ fn main() {
|
||||
mut_raw_mut();
|
||||
partially_invalidate_mut();
|
||||
drop_after_sharing();
|
||||
direct_mut_to_const_raw();
|
||||
two_raw();
|
||||
shr_and_raw();
|
||||
disjoint_mutable_subborrows();
|
||||
@ -28,6 +29,18 @@ fn main() {
|
||||
mut_below_shr();
|
||||
wide_raw_ptr_in_tuple();
|
||||
not_unpin_not_protected();
|
||||
write_does_not_invalidate_all_aliases();
|
||||
}
|
||||
|
||||
#[allow(unused_assignments)]
|
||||
fn local_addr_of_mut() {
|
||||
let mut local = 0;
|
||||
let ptr = ptr::addr_of_mut!(local);
|
||||
// In SB, `local` and `*ptr` would have different tags, but in TB they have the same tag.
|
||||
local = 1;
|
||||
unsafe { *ptr = 2 };
|
||||
local = 3;
|
||||
unsafe { *ptr = 4 };
|
||||
}
|
||||
|
||||
// Tree Borrows has no issue with several mutable references existing
|
||||
@ -172,12 +185,12 @@ fn drop_after_sharing() {
|
||||
|
||||
// Make sure that coercing &mut T to *const T produces a writeable pointer.
|
||||
fn direct_mut_to_const_raw() {
|
||||
// TODO: This is currently disabled, waiting on a decision on <https://github.com/rust-lang/rust/issues/56604>
|
||||
/*let x = &mut 0;
|
||||
let x = &mut 0;
|
||||
let y: *const i32 = x;
|
||||
unsafe { *(y as *mut i32) = 1; }
|
||||
unsafe {
|
||||
*(y as *mut i32) = 1;
|
||||
}
|
||||
assert_eq!(*x, 1);
|
||||
*/
|
||||
}
|
||||
|
||||
// Make sure that we can create two raw pointers from a mutable reference and use them both.
|
||||
@ -298,3 +311,31 @@ fn not_unpin_not_protected() {
|
||||
drop(unsafe { Box::from_raw(raw) });
|
||||
});
|
||||
}
|
||||
|
||||
fn write_does_not_invalidate_all_aliases() {
|
||||
// In TB there are other ways to do that (`addr_of!(*x)` has the same tag as `x`),
|
||||
// but let's still make sure this SB test keeps working.
|
||||
|
||||
mod other {
|
||||
/// Some private memory to store stuff in.
|
||||
static mut S: *mut i32 = 0 as *mut i32;
|
||||
|
||||
pub fn lib1(x: &&mut i32) {
|
||||
unsafe {
|
||||
S = (x as *const &mut i32).cast::<*mut i32>().read();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lib2() {
|
||||
unsafe {
|
||||
*S = 1337;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let x = &mut 0;
|
||||
other::lib1(&x);
|
||||
*x = 42; // a write to x -- invalidates other pointers?
|
||||
other::lib2();
|
||||
assert_eq!(*x, 1337); // oops, the value changed! I guess not all pointers were invalidated
|
||||
}
|
||||
|
@ -5,8 +5,8 @@
|
||||
#![feature(ptr_internals)]
|
||||
|
||||
#[path = "../../utils/mod.rs"]
|
||||
#[macro_use]
|
||||
mod utils;
|
||||
use utils::macros::*;
|
||||
|
||||
use core::ptr::Unique;
|
||||
|
||||
|
@ -5,8 +5,8 @@
|
||||
#![feature(vec_into_raw_parts)]
|
||||
|
||||
#[path = "../../utils/mod.rs"]
|
||||
#[macro_use]
|
||||
mod utils;
|
||||
use utils::macros::*;
|
||||
|
||||
// Check general handling of `Unique`:
|
||||
// there is no *explicit* `Unique` being used here, but there is one
|
||||
|
29
src/tools/miri/tests/utils/fs.rs
Normal file
29
src/tools/miri/tests/utils/fs.rs
Normal 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)
|
||||
}
|
@ -9,7 +9,7 @@
|
||||
/// The id obtained can be passed directly to `print_state!`.
|
||||
macro_rules! alloc_id {
|
||||
($ptr:expr) => {
|
||||
crate::utils::miri_extern::miri_get_alloc_id($ptr as *const u8 as *const ())
|
||||
$crate::utils::miri_get_alloc_id($ptr as *const u8 as *const ())
|
||||
};
|
||||
}
|
||||
|
||||
@ -22,10 +22,10 @@ macro_rules! alloc_id {
|
||||
/// tags that have not been given a name. Defaults to `false`.
|
||||
macro_rules! print_state {
|
||||
($alloc_id:expr) => {
|
||||
crate::utils::macros::print_state!($alloc_id, false);
|
||||
print_state!($alloc_id, false);
|
||||
};
|
||||
($alloc_id:expr, $show:expr) => {
|
||||
crate::utils::miri_extern::miri_print_borrow_state($alloc_id, $show);
|
||||
$crate::utils::miri_print_borrow_state($alloc_id, $show);
|
||||
};
|
||||
}
|
||||
|
||||
@ -42,20 +42,16 @@ macro_rules! print_state {
|
||||
/// `stringify!($ptr)` the name of `ptr` in the source code.
|
||||
macro_rules! name {
|
||||
($ptr:expr, $name:expr) => {
|
||||
crate::utils::macros::name!($ptr => 0, $name);
|
||||
name!($ptr => 0, $name);
|
||||
};
|
||||
($ptr:expr) => {
|
||||
crate::utils::macros::name!($ptr => 0, stringify!($ptr));
|
||||
name!($ptr => 0, stringify!($ptr));
|
||||
};
|
||||
($ptr:expr => $nth_parent:expr) => {
|
||||
crate::utils::macros::name!($ptr => $nth_parent, stringify!($ptr));
|
||||
name!($ptr => $nth_parent, stringify!($ptr));
|
||||
};
|
||||
($ptr:expr => $nth_parent:expr, $name:expr) => {
|
||||
let name = $name.as_bytes();
|
||||
crate::utils::miri_extern::miri_pointer_name($ptr as *const u8 as *const (), $nth_parent, name);
|
||||
$crate::utils::miri_pointer_name($ptr as *const u8 as *const (), $nth_parent, name);
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) use alloc_id;
|
||||
pub(crate) use name;
|
||||
pub(crate) use print_state;
|
||||
|
@ -1,5 +1,3 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
#[repr(C)]
|
||||
/// Layout of the return value of `miri_resolve_frame`,
|
||||
/// with fields in the exact same order.
|
||||
|
@ -1,2 +1,10 @@
|
||||
pub mod macros;
|
||||
pub mod miri_extern;
|
||||
#![allow(dead_code)]
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
mod fs;
|
||||
mod miri_extern;
|
||||
|
||||
pub use fs::*;
|
||||
pub use miri_extern::*;
|
||||
|
Loading…
Reference in New Issue
Block a user