add -Zmiri-many-seeds flag to the driver itself
This commit is contained in:
parent
bba6f0a6b2
commit
d80f319121
5 changed files with 172 additions and 94 deletions
|
@ -26,11 +26,17 @@ extern crate rustc_span;
|
|||
|
||||
use std::env::{self, VarError};
|
||||
use std::num::NonZero;
|
||||
use std::ops::Range;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use miri::{BacktraceStyle, BorrowTrackerMethod, ProvenanceMode, RetagFields, ValidationMode};
|
||||
use miri::{
|
||||
BacktraceStyle, BorrowTrackerMethod, MiriConfig, ProvenanceMode, RetagFields, ValidationMode,
|
||||
};
|
||||
use rustc_abi::ExternAbi;
|
||||
use rustc_data_structures::sync;
|
||||
use rustc_data_structures::sync::Lrc;
|
||||
use rustc_driver::Compilation;
|
||||
use rustc_hir::def_id::LOCAL_CRATE;
|
||||
|
@ -52,7 +58,64 @@ use rustc_span::def_id::DefId;
|
|||
use tracing::debug;
|
||||
|
||||
struct MiriCompilerCalls {
|
||||
miri_config: miri::MiriConfig,
|
||||
miri_config: Option<MiriConfig>,
|
||||
many_seeds: Option<Range<u32>>,
|
||||
}
|
||||
|
||||
impl MiriCompilerCalls {
|
||||
fn new(miri_config: MiriConfig, many_seeds: Option<Range<u32>>) -> Self {
|
||||
Self { miri_config: Some(miri_config), many_seeds }
|
||||
}
|
||||
}
|
||||
|
||||
fn entry_fn(tcx: TyCtxt<'_>) -> (DefId, EntryFnType) {
|
||||
if let Some(entry_def) = tcx.entry_fn(()) {
|
||||
return entry_def;
|
||||
}
|
||||
// Look for a symbol in the local crate named `miri_start`, and treat that as the entry point.
|
||||
let sym = tcx.exported_symbols(LOCAL_CRATE).iter().find_map(|(sym, _)| {
|
||||
if sym.symbol_name_for_local_instance(tcx).name == "miri_start" { Some(sym) } else { None }
|
||||
});
|
||||
if let Some(ExportedSymbol::NonGeneric(id)) = sym {
|
||||
let start_def_id = id.expect_local();
|
||||
let start_span = tcx.def_span(start_def_id);
|
||||
|
||||
let expected_sig = ty::Binder::dummy(tcx.mk_fn_sig(
|
||||
[tcx.types.isize, Ty::new_imm_ptr(tcx, Ty::new_imm_ptr(tcx, tcx.types.u8))],
|
||||
tcx.types.isize,
|
||||
false,
|
||||
hir::Safety::Safe,
|
||||
ExternAbi::Rust,
|
||||
));
|
||||
|
||||
let correct_func_sig = check_function_signature(
|
||||
tcx,
|
||||
ObligationCause::new(start_span, start_def_id, ObligationCauseCode::Misc),
|
||||
*id,
|
||||
expected_sig,
|
||||
)
|
||||
.is_ok();
|
||||
|
||||
if correct_func_sig {
|
||||
(*id, EntryFnType::Start)
|
||||
} else {
|
||||
tcx.dcx().fatal(
|
||||
"`miri_start` must have the following signature:\n\
|
||||
fn miri_start(argc: isize, argv: *const *const u8) -> isize",
|
||||
);
|
||||
}
|
||||
} else {
|
||||
tcx.dcx().fatal(
|
||||
"Miri can only run programs that have a main function.\n\
|
||||
Alternatively, you can export a `miri_start` function:\n\
|
||||
\n\
|
||||
#[cfg(miri)]\n\
|
||||
#[no_mangle]\n\
|
||||
fn miri_start(argc: isize, argv: *const *const u8) -> isize {\
|
||||
\n // Call the actual start function that your project implements, based on your target's conventions.\n\
|
||||
}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl rustc_driver::Callbacks for MiriCompilerCalls {
|
||||
|
@ -87,7 +150,7 @@ impl rustc_driver::Callbacks for MiriCompilerCalls {
|
|||
}
|
||||
|
||||
let (entry_def_id, entry_type) = entry_fn(tcx);
|
||||
let mut config = self.miri_config.clone();
|
||||
let mut config = self.miri_config.take().expect("after_analysis must only be called once");
|
||||
|
||||
// Add filename to `miri` arguments.
|
||||
config.args.insert(0, tcx.sess.io.input.filestem().to_string());
|
||||
|
@ -111,12 +174,31 @@ impl rustc_driver::Callbacks for MiriCompilerCalls {
|
|||
optimizations is usually marginal at best.");
|
||||
}
|
||||
|
||||
if let Some(return_code) = miri::eval_entry(tcx, entry_def_id, entry_type, config) {
|
||||
std::process::exit(i32::try_from(return_code).expect("Return value was too large!"));
|
||||
if let Some(many_seeds) = self.many_seeds.take() {
|
||||
assert!(config.seed.is_none());
|
||||
sync::par_for_each_in(many_seeds, |seed| {
|
||||
let mut config = config.clone();
|
||||
config.seed = Some(seed.into());
|
||||
eprintln!("Trying seed: {seed}");
|
||||
let return_code = miri::eval_entry(tcx, entry_def_id, entry_type, config)
|
||||
.unwrap_or(rustc_driver::EXIT_FAILURE);
|
||||
if return_code != rustc_driver::EXIT_SUCCESS {
|
||||
eprintln!("FAILING SEED: {seed}");
|
||||
tcx.dcx().abort_if_errors(); // exits with a different error message
|
||||
std::process::exit(return_code);
|
||||
}
|
||||
});
|
||||
std::process::exit(rustc_driver::EXIT_SUCCESS);
|
||||
} else {
|
||||
let return_code = miri::eval_entry(tcx, entry_def_id, entry_type, config)
|
||||
.unwrap_or_else(|| {
|
||||
tcx.dcx().abort_if_errors();
|
||||
rustc_driver::EXIT_FAILURE
|
||||
});
|
||||
std::process::exit(return_code);
|
||||
}
|
||||
tcx.dcx().abort_if_errors();
|
||||
|
||||
Compilation::Stop
|
||||
// Unreachable.
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -241,21 +323,28 @@ fn rustc_logger_config() -> rustc_log::LoggerConfig {
|
|||
cfg
|
||||
}
|
||||
|
||||
/// The global logger can only be set once per process, so track
|
||||
/// whether that already happened.
|
||||
static LOGGER_INITED: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
fn init_early_loggers(early_dcx: &EarlyDiagCtxt) {
|
||||
// Now for rustc. We only initialize `rustc` if the env var is set (so the user asked for it).
|
||||
// If it is not set, we avoid initializing now so that we can initialize later with our custom
|
||||
// settings, and *not* log anything for what happens before `miri` gets started.
|
||||
if env::var_os("RUSTC_LOG").is_some() {
|
||||
rustc_driver::init_logger(early_dcx, rustc_logger_config());
|
||||
assert!(!LOGGER_INITED.swap(true, Ordering::AcqRel));
|
||||
}
|
||||
}
|
||||
|
||||
fn init_late_loggers(early_dcx: &EarlyDiagCtxt, tcx: TyCtxt<'_>) {
|
||||
// If `RUSTC_LOG` is not set, then `init_early_loggers` did not call
|
||||
// `rustc_driver::init_logger`, so we have to do this now.
|
||||
if env::var_os("RUSTC_LOG").is_none() {
|
||||
// If the logger is not yet initialized, initialize it.
|
||||
if !LOGGER_INITED.swap(true, Ordering::AcqRel) {
|
||||
rustc_driver::init_logger(early_dcx, rustc_logger_config());
|
||||
}
|
||||
// There's a little race condition here in many-seeds mode, where we don't wait for the thread
|
||||
// that is doing the initializing. But if you want to debug things with extended logging you
|
||||
// probably won't use many-seeds mode anyway.
|
||||
|
||||
// If `MIRI_BACKTRACE` is set and `RUSTC_CTFE_BACKTRACE` is not, set `RUSTC_CTFE_BACKTRACE`.
|
||||
// Do this late, so we ideally only apply this to Miri's errors.
|
||||
|
@ -270,25 +359,14 @@ fn init_late_loggers(early_dcx: &EarlyDiagCtxt, tcx: TyCtxt<'_>) {
|
|||
}
|
||||
|
||||
/// Execute a compiler with the given CLI arguments and callbacks.
|
||||
fn run_compiler(
|
||||
mut args: Vec<String>,
|
||||
target_crate: bool,
|
||||
fn run_compiler_and_exit(
|
||||
args: &[String],
|
||||
callbacks: &mut (dyn rustc_driver::Callbacks + Send),
|
||||
using_internal_features: std::sync::Arc<std::sync::atomic::AtomicBool>,
|
||||
using_internal_features: Arc<std::sync::atomic::AtomicBool>,
|
||||
) -> ! {
|
||||
// Don't insert `MIRI_DEFAULT_ARGS`, in particular, `--cfg=miri`, if we are building
|
||||
// a "host" crate. That may cause procedural macros (and probably build scripts) to
|
||||
// depend on Miri-only symbols, such as `miri_resolve_frame`:
|
||||
// https://github.com/rust-lang/miri/issues/1760
|
||||
if target_crate {
|
||||
// Some options have different defaults in Miri than in plain rustc; apply those by making
|
||||
// them the first arguments after the binary name (but later arguments can overwrite them).
|
||||
args.splice(1..1, miri::MIRI_DEFAULT_ARGS.iter().map(ToString::to_string));
|
||||
}
|
||||
|
||||
// Invoke compiler, and handle return code.
|
||||
let exit_code = rustc_driver::catch_with_exit_code(move || {
|
||||
rustc_driver::RunCompiler::new(&args, callbacks)
|
||||
rustc_driver::RunCompiler::new(args, callbacks)
|
||||
.set_using_internal_features(using_internal_features)
|
||||
.run();
|
||||
Ok(())
|
||||
|
@ -311,6 +389,18 @@ fn parse_rate(input: &str) -> Result<f64, &'static str> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Parses a seed range
|
||||
///
|
||||
/// This function is used for the `-Zmiri-many-seeds` flag. It expects the range in the form
|
||||
/// `<from>..<to>`. `<from>` is inclusive, `<to>` is exclusive. `<from>` can be omitted,
|
||||
/// in which case it is assumed to be `0`.
|
||||
fn parse_range(val: &str) -> Result<Range<u32>, &'static str> {
|
||||
let (from, to) = val.split_once("..").ok_or("expected `from..to`")?;
|
||||
let from: u32 = if from.is_empty() { 0 } else { from.parse().map_err(|_| "invalid `from`")? };
|
||||
let to: u32 = to.parse().map_err(|_| "invalid `to`")?;
|
||||
Ok(from..to)
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
fn jemalloc_magic() {
|
||||
// These magic runes are copied from
|
||||
|
@ -349,56 +439,6 @@ fn jemalloc_magic() {
|
|||
}
|
||||
}
|
||||
|
||||
fn entry_fn(tcx: TyCtxt<'_>) -> (DefId, EntryFnType) {
|
||||
if let Some(entry_def) = tcx.entry_fn(()) {
|
||||
return entry_def;
|
||||
}
|
||||
// Look for a symbol in the local crate named `miri_start`, and treat that as the entry point.
|
||||
let sym = tcx.exported_symbols(LOCAL_CRATE).iter().find_map(|(sym, _)| {
|
||||
if sym.symbol_name_for_local_instance(tcx).name == "miri_start" { Some(sym) } else { None }
|
||||
});
|
||||
if let Some(ExportedSymbol::NonGeneric(id)) = sym {
|
||||
let start_def_id = id.expect_local();
|
||||
let start_span = tcx.def_span(start_def_id);
|
||||
|
||||
let expected_sig = ty::Binder::dummy(tcx.mk_fn_sig(
|
||||
[tcx.types.isize, Ty::new_imm_ptr(tcx, Ty::new_imm_ptr(tcx, tcx.types.u8))],
|
||||
tcx.types.isize,
|
||||
false,
|
||||
hir::Safety::Safe,
|
||||
ExternAbi::Rust,
|
||||
));
|
||||
|
||||
let correct_func_sig = check_function_signature(
|
||||
tcx,
|
||||
ObligationCause::new(start_span, start_def_id, ObligationCauseCode::Misc),
|
||||
*id,
|
||||
expected_sig,
|
||||
)
|
||||
.is_ok();
|
||||
|
||||
if correct_func_sig {
|
||||
(*id, EntryFnType::Start)
|
||||
} else {
|
||||
tcx.dcx().fatal(
|
||||
"`miri_start` must have the following signature:\n\
|
||||
fn miri_start(argc: isize, argv: *const *const u8) -> isize",
|
||||
);
|
||||
}
|
||||
} else {
|
||||
tcx.dcx().fatal(
|
||||
"Miri can only run programs that have a main function.\n\
|
||||
Alternatively, you can export a `miri_start` function:\n\
|
||||
\n\
|
||||
#[cfg(miri)]\n\
|
||||
#[no_mangle]\n\
|
||||
fn miri_start(argc: isize, argv: *const *const u8) -> isize {\
|
||||
\n // Call the actual start function that your project implements, based on your target's conventions.\n\
|
||||
}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
jemalloc_magic();
|
||||
|
@ -431,10 +471,21 @@ fn main() {
|
|||
panic!("invalid `MIRI_BE_RUSTC` value: {crate_kind:?}")
|
||||
};
|
||||
|
||||
// We cannot use `rustc_driver::main` as we need to adjust the CLI arguments.
|
||||
run_compiler(
|
||||
args,
|
||||
target_crate,
|
||||
let mut args = args;
|
||||
// Don't insert `MIRI_DEFAULT_ARGS`, in particular, `--cfg=miri`, if we are building
|
||||
// a "host" crate. That may cause procedural macros (and probably build scripts) to
|
||||
// depend on Miri-only symbols, such as `miri_resolve_frame`:
|
||||
// https://github.com/rust-lang/miri/issues/1760
|
||||
if target_crate {
|
||||
// Splice in the default arguments after the program name.
|
||||
// Some options have different defaults in Miri than in plain rustc; apply those by making
|
||||
// them the first arguments after the binary name (but later arguments can overwrite them).
|
||||
args.splice(1..1, miri::MIRI_DEFAULT_ARGS.iter().map(ToString::to_string));
|
||||
}
|
||||
|
||||
// We cannot use `rustc_driver::main` as we want it to use `args` as the CLI arguments.
|
||||
run_compiler_and_exit(
|
||||
&args,
|
||||
&mut MiriBeRustCompilerCalls { target_crate },
|
||||
using_internal_features,
|
||||
)
|
||||
|
@ -448,7 +499,8 @@ fn main() {
|
|||
init_early_loggers(&early_dcx);
|
||||
|
||||
// Parse our arguments and split them across `rustc` and `miri`.
|
||||
let mut miri_config = miri::MiriConfig::default();
|
||||
let mut many_seeds: Option<Range<u32>> = None;
|
||||
let mut miri_config = MiriConfig::default();
|
||||
miri_config.env = env_snapshot;
|
||||
|
||||
let mut rustc_args = vec![];
|
||||
|
@ -463,6 +515,8 @@ fn main() {
|
|||
if rustc_args.is_empty() {
|
||||
// Very first arg: binary name.
|
||||
rustc_args.push(arg);
|
||||
// Also add the default arguments.
|
||||
rustc_args.extend(miri::MIRI_DEFAULT_ARGS.iter().map(ToString::to_string));
|
||||
} else if after_dashdash {
|
||||
// Everything that comes after `--` is forwarded to the interpreted crate.
|
||||
miri_config.args.push(arg);
|
||||
|
@ -544,13 +598,19 @@ fn main() {
|
|||
_ => show_error!("`-Zmiri-retag-fields` can only be `all`, `none`, or `scalar`"),
|
||||
};
|
||||
} else if let Some(param) = arg.strip_prefix("-Zmiri-seed=") {
|
||||
if miri_config.seed.is_some() {
|
||||
show_error!("Cannot specify -Zmiri-seed multiple times!");
|
||||
}
|
||||
let seed = param.parse::<u64>().unwrap_or_else(|_| {
|
||||
show_error!("-Zmiri-seed must be an integer that fits into u64")
|
||||
});
|
||||
miri_config.seed = Some(seed);
|
||||
} else if let Some(param) = arg.strip_prefix("-Zmiri-many-seeds=") {
|
||||
let range = parse_range(param).unwrap_or_else(|err| {
|
||||
show_error!(
|
||||
"-Zmiri-many-seeds requires a range in the form `from..to` or `..to`: {err}"
|
||||
)
|
||||
});
|
||||
many_seeds = Some(range);
|
||||
} else if arg == "-Zmiri-many-seeds" {
|
||||
many_seeds = Some(0..64);
|
||||
} else if let Some(_param) = arg.strip_prefix("-Zmiri-env-exclude=") {
|
||||
show_error!(
|
||||
"`-Zmiri-env-exclude` has been removed; unset env vars before starting Miri instead"
|
||||
|
@ -665,13 +725,23 @@ fn main() {
|
|||
"Tree Borrows does not support integer-to-pointer casts, and is hence not compatible with permissive provenance"
|
||||
);
|
||||
}
|
||||
// You can set either one seed or many.
|
||||
if many_seeds.is_some() && miri_config.seed.is_some() {
|
||||
show_error!("Only one of `-Zmiri-seed` and `-Zmiri-many-seeds can be set");
|
||||
}
|
||||
if many_seeds.is_some() && !rustc_args.iter().any(|arg| arg.starts_with("-Zthreads=")) {
|
||||
// Ensure we have parallelism for many-seeds mode.
|
||||
rustc_args.push(format!(
|
||||
"-Zthreads={}",
|
||||
std::thread::available_parallelism().map_or(1, |n| n.get())
|
||||
));
|
||||
}
|
||||
|
||||
debug!("rustc arguments: {:?}", rustc_args);
|
||||
debug!("crate arguments: {:?}", miri_config.args);
|
||||
run_compiler(
|
||||
rustc_args,
|
||||
/* target_crate: */ true,
|
||||
&mut MiriCompilerCalls { miri_config },
|
||||
run_compiler_and_exit(
|
||||
&rustc_args,
|
||||
&mut MiriCompilerCalls::new(miri_config, many_seeds),
|
||||
using_internal_features,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ use crate::*;
|
|||
/// Details of premature program termination.
|
||||
pub enum TerminationInfo {
|
||||
Exit {
|
||||
code: i64,
|
||||
code: i32,
|
||||
leak_check: bool,
|
||||
},
|
||||
Abort(String),
|
||||
|
@ -214,7 +214,7 @@ pub fn prune_stacktrace<'tcx>(
|
|||
pub fn report_error<'tcx>(
|
||||
ecx: &InterpCx<'tcx, MiriMachine<'tcx>>,
|
||||
e: InterpErrorInfo<'tcx>,
|
||||
) -> Option<(i64, bool)> {
|
||||
) -> Option<(i32, bool)> {
|
||||
use InterpErrorKind::*;
|
||||
use UndefinedBehaviorInfo::*;
|
||||
|
||||
|
|
|
@ -249,6 +249,13 @@ impl<'tcx> MainThreadState<'tcx> {
|
|||
// Figure out exit code.
|
||||
let ret_place = this.machine.main_fn_ret_place.clone().unwrap();
|
||||
let exit_code = this.read_target_isize(&ret_place)?;
|
||||
// Rust uses `isize` but the underlying type of an exit code is `i32`.
|
||||
// Do a saturating cast.
|
||||
let exit_code = i32::try_from(exit_code).unwrap_or(if exit_code >= 0 {
|
||||
i32::MAX
|
||||
} else {
|
||||
i32::MIN
|
||||
});
|
||||
// Deal with our thread-local memory. We do *not* want to actually free it, instead we consider TLS
|
||||
// to be like a global `static`, so that all memory reached by it is considered to "not leak".
|
||||
this.terminate_active_thread(TlsAllocAction::Leak)?;
|
||||
|
@ -421,7 +428,7 @@ pub fn create_ecx<'tcx>(
|
|||
}
|
||||
|
||||
/// Evaluates the entry function specified by `entry_id`.
|
||||
/// Returns `Some(return_code)` if program executed completed.
|
||||
/// Returns `Some(return_code)` if program execution completed.
|
||||
/// Returns `None` if an evaluation error occurred.
|
||||
#[expect(clippy::needless_lifetimes)]
|
||||
pub fn eval_entry<'tcx>(
|
||||
|
@ -429,7 +436,7 @@ pub fn eval_entry<'tcx>(
|
|||
entry_id: DefId,
|
||||
entry_type: EntryFnType,
|
||||
config: MiriConfig,
|
||||
) -> Option<i64> {
|
||||
) -> Option<i32> {
|
||||
// Copy setting before we move `config`.
|
||||
let ignore_leaks = config.ignore_leaks;
|
||||
|
||||
|
|
|
@ -428,7 +428,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
"exit" => {
|
||||
let [code] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
let code = this.read_scalar(code)?.to_i32()?;
|
||||
throw_machine_stop!(TerminationInfo::Exit { code: code.into(), leak_check: false });
|
||||
throw_machine_stop!(TerminationInfo::Exit { code, leak_check: false });
|
||||
}
|
||||
"abort" => {
|
||||
let [] = this.check_shim(abi, Conv::C, link_name, args)?;
|
||||
|
|
|
@ -552,8 +552,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
// Miscellaneous
|
||||
"ExitProcess" => {
|
||||
let [code] = this.check_shim(abi, sys_conv, link_name, args)?;
|
||||
let code = this.read_scalar(code)?.to_u32()?;
|
||||
throw_machine_stop!(TerminationInfo::Exit { code: code.into(), leak_check: false });
|
||||
// Windows technically uses u32, but we unify everything to a Unix-style i32.
|
||||
let code = this.read_scalar(code)?.to_i32()?;
|
||||
throw_machine_stop!(TerminationInfo::Exit { code, leak_check: false });
|
||||
}
|
||||
"SystemFunction036" => {
|
||||
// used by getrandom 0.1
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue