From d80f319121f65d34352c560c4c54e8fe0a69de8f Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 23 Dec 2024 11:49:03 +0100 Subject: [PATCH] add -Zmiri-many-seeds flag to the driver itself --- src/tools/miri/src/bin/miri.rs | 244 +++++++++++------- src/tools/miri/src/diagnostics.rs | 4 +- src/tools/miri/src/eval.rs | 11 +- src/tools/miri/src/shims/foreign_items.rs | 2 +- .../miri/src/shims/windows/foreign_items.rs | 5 +- 5 files changed, 172 insertions(+), 94 deletions(-) diff --git a/src/tools/miri/src/bin/miri.rs b/src/tools/miri/src/bin/miri.rs index 5248c9d186b4..ca3704e0655e 100644 --- a/src/tools/miri/src/bin/miri.rs +++ b/src/tools/miri/src/bin/miri.rs @@ -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, + many_seeds: Option>, +} + +impl MiriCompilerCalls { + fn new(miri_config: MiriConfig, many_seeds: Option>) -> 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, - target_crate: bool, +fn run_compiler_and_exit( + args: &[String], callbacks: &mut (dyn rustc_driver::Callbacks + Send), - using_internal_features: std::sync::Arc, + using_internal_features: Arc, ) -> ! { - // 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 { } } +/// Parses a seed range +/// +/// This function is used for the `-Zmiri-many-seeds` flag. It expects the range in the form +/// `..`. `` is inclusive, `` is exclusive. `` can be omitted, +/// in which case it is assumed to be `0`. +fn parse_range(val: &str) -> Result, &'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> = 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::().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, ) } diff --git a/src/tools/miri/src/diagnostics.rs b/src/tools/miri/src/diagnostics.rs index 6b5646d5473f..1a12d4139c71 100644 --- a/src/tools/miri/src/diagnostics.rs +++ b/src/tools/miri/src/diagnostics.rs @@ -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::*; diff --git a/src/tools/miri/src/eval.rs b/src/tools/miri/src/eval.rs index 1df1d08802a8..eaf4b30c6606 100644 --- a/src/tools/miri/src/eval.rs +++ b/src/tools/miri/src/eval.rs @@ -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 { +) -> Option { // Copy setting before we move `config`. let ignore_leaks = config.ignore_leaks; diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs index 8c8850ba7e0a..6c8ccc839859 100644 --- a/src/tools/miri/src/shims/foreign_items.rs +++ b/src/tools/miri/src/shims/foreign_items.rs @@ -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)?; diff --git a/src/tools/miri/src/shims/windows/foreign_items.rs b/src/tools/miri/src/shims/windows/foreign_items.rs index fe4d2158ff95..0bf56c3d005f 100644 --- a/src/tools/miri/src/shims/windows/foreign_items.rs +++ b/src/tools/miri/src/shims/windows/foreign_items.rs @@ -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