| #![feature(rustc_private, stmt_expr_attributes)] |
| #![allow( |
| clippy::manual_range_contains, |
| clippy::useless_format, |
| clippy::field_reassign_with_default, |
| clippy::needless_lifetimes, |
| rustc::diagnostic_outside_of_impl, |
| rustc::untranslatable_diagnostic |
| )] |
| |
| // The rustc crates we need |
| extern crate rustc_abi; |
| extern crate rustc_codegen_ssa; |
| extern crate rustc_data_structures; |
| extern crate rustc_driver; |
| extern crate rustc_hir; |
| extern crate rustc_hir_analysis; |
| extern crate rustc_interface; |
| extern crate rustc_log; |
| extern crate rustc_middle; |
| extern crate rustc_session; |
| extern crate rustc_span; |
| extern crate rustc_target; |
| |
| /// See docs in https://github.com/rust-lang/rust/blob/HEAD/compiler/rustc/src/main.rs |
| /// and https://github.com/rust-lang/rust/pull/146627 for why we need this. |
| /// |
| /// FIXME(madsmtm): This is loaded from the sysroot that was built with the other `rustc` crates |
| /// above, instead of via Cargo as you'd normally do. This is currently needed for LTO due to |
| /// https://github.com/rust-lang/cc-rs/issues/1613. |
| #[cfg(feature = "jemalloc")] |
| // Make sure `--all-features` works: only Linux and macOS actually use jemalloc, and not on arm32. |
| #[cfg(all( |
| any(target_os = "linux", target_os = "macos"), |
| any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64"), |
| ))] |
| extern crate tikv_jemalloc_sys as _; |
| |
| mod log; |
| |
| use std::env; |
| use std::num::{NonZero, NonZeroI32}; |
| use std::ops::Range; |
| use std::rc::Rc; |
| use std::str::FromStr; |
| use std::sync::Once; |
| use std::sync::atomic::{AtomicU32, Ordering}; |
| |
| use miri::{ |
| BacktraceStyle, BorrowTrackerMethod, GenmcConfig, GenmcCtx, MiriConfig, MiriEntryFnType, |
| ProvenanceMode, TreeBorrowsParams, ValidationMode, run_genmc_mode, |
| }; |
| use rustc_abi::ExternAbi; |
| use rustc_codegen_ssa::traits::CodegenBackend; |
| use rustc_data_structures::sync::{self, DynSync}; |
| use rustc_driver::Compilation; |
| use rustc_hir::def_id::LOCAL_CRATE; |
| use rustc_hir::{self as hir, Node}; |
| use rustc_hir_analysis::check::check_function_signature; |
| use rustc_interface::interface::Config; |
| use rustc_interface::util::DummyCodegenBackend; |
| use rustc_log::tracing::debug; |
| use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; |
| use rustc_middle::middle::exported_symbols::{ |
| ExportedSymbol, SymbolExportInfo, SymbolExportKind, SymbolExportLevel, |
| }; |
| use rustc_middle::query::LocalCrate; |
| use rustc_middle::traits::{ObligationCause, ObligationCauseCode}; |
| use rustc_middle::ty::{self, Ty, TyCtxt}; |
| use rustc_session::EarlyDiagCtxt; |
| use rustc_session::config::{CrateType, ErrorOutputType, OptLevel, Options}; |
| use rustc_span::def_id::DefId; |
| use rustc_target::spec::Target; |
| |
| use crate::log::setup::{deinit_loggers, init_early_loggers, init_late_loggers}; |
| |
| struct MiriCompilerCalls { |
| miri_config: Option<MiriConfig>, |
| many_seeds: Option<ManySeedsConfig>, |
| } |
| |
| struct ManySeedsConfig { |
| seeds: Range<u32>, |
| keep_going: bool, |
| } |
| |
| impl MiriCompilerCalls { |
| fn new(miri_config: MiriConfig, many_seeds: Option<ManySeedsConfig>) -> Self { |
| Self { miri_config: Some(miri_config), many_seeds } |
| } |
| } |
| |
| fn entry_fn(tcx: TyCtxt<'_>) -> (DefId, MiriEntryFnType) { |
| if let Some((def_id, entry_type)) = tcx.entry_fn(()) { |
| return (def_id, MiriEntryFnType::Rustc(entry_type)); |
| } |
| // Look for a symbol in the local crate named `miri_start`, and treat that as the entry point. |
| let sym = tcx.exported_non_generic_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, MiriEntryFnType::MiriStart) |
| } 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\ |
| #[unsafe(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 run_many_seeds( |
| many_seeds: ManySeedsConfig, |
| eval_entry_once: impl Fn(u64) -> Result<(), NonZeroI32> + DynSync, |
| ) -> Result<(), NonZeroI32> { |
| let exit_code = |
| sync::IntoDynSyncSend(AtomicU32::new(rustc_driver::EXIT_SUCCESS.cast_unsigned())); |
| let num_failed = sync::IntoDynSyncSend(AtomicU32::new(0)); |
| sync::par_for_each_in(many_seeds.seeds.clone(), |&seed| { |
| if let Err(return_code) = eval_entry_once(seed.into()) { |
| eprintln!("FAILING SEED: {seed}"); |
| if !many_seeds.keep_going { |
| // `abort_if_errors` would unwind but would not actually stop miri, since |
| // `par_for_each` waits for the rest of the threads to finish. |
| exit(return_code.get()); |
| } |
| // Preserve the "maximum" return code (when interpreted as `u32`), to make |
| // the result order-independent and to make it 0 only if all executions were 0. |
| exit_code.fetch_max(return_code.get().cast_unsigned(), Ordering::Relaxed); |
| num_failed.fetch_add(1, Ordering::Relaxed); |
| } |
| }); |
| let num_failed = num_failed.0.into_inner(); |
| let exit_code = exit_code.0.into_inner().cast_signed(); |
| if num_failed > 0 { |
| eprintln!("{num_failed}/{total} SEEDS FAILED", total = many_seeds.seeds.count()); |
| Err(NonZeroI32::new(exit_code).unwrap()) |
| } else { |
| assert!(exit_code == 0); |
| Ok(()) |
| } |
| } |
| |
| /// Generates the codegen backend for code that Miri will interpret: we basically |
| /// use the dummy backend, except that we put the LLVM backend in charge of |
| /// target features. |
| fn make_miri_codegen_backend(opts: &Options, target: &Target) -> Box<dyn CodegenBackend> { |
| let early_dcx = EarlyDiagCtxt::new(opts.error_format); |
| |
| // Use the target_config method of the default codegen backend (eg LLVM) to ensure the |
| // calculated target features match said backend by respecting eg -Ctarget-cpu. |
| let target_config_backend = |
| rustc_interface::util::get_codegen_backend(&early_dcx, &opts.sysroot, None, target); |
| let target_config_backend_init = Once::new(); |
| |
| Box::new(DummyCodegenBackend { |
| target_config_override: Some(Box::new(move |sess| { |
| target_config_backend_init.call_once(|| target_config_backend.init(sess)); |
| target_config_backend.target_config(sess) |
| })), |
| }) |
| } |
| |
| impl rustc_driver::Callbacks for MiriCompilerCalls { |
| fn config(&mut self, config: &mut rustc_interface::interface::Config) { |
| config.make_codegen_backend = Some(Box::new(make_miri_codegen_backend)); |
| } |
| |
| fn after_analysis<'tcx>( |
| &mut self, |
| _: &rustc_interface::interface::Compiler, |
| tcx: TyCtxt<'tcx>, |
| ) -> Compilation { |
| // Compilation is done, interpretation is starting. Deal with diagnostics from the |
| // compilation part. We cannot call `sess.finish_diagnostics()` as then "aborting due to |
| // previous errors" gets printed twice. |
| tcx.dcx().emit_stashed_diagnostics(); |
| tcx.dcx().abort_if_errors(); |
| tcx.dcx().flush_delayed(); |
| |
| // Miri is taking over. Start logging. |
| init_late_loggers(&EarlyDiagCtxt::new(tcx.sess.opts.error_format), tcx); |
| |
| // Find the entry point. |
| if !tcx.crate_types().contains(&CrateType::Executable) { |
| tcx.dcx().fatal("miri only makes sense on bin crates"); |
| } |
| let (entry_def_id, entry_type) = entry_fn(tcx); |
| |
| // Obtain and complete the Miri configuration. |
| 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()); |
| |
| // Adjust working directory for interpretation. |
| if let Some(cwd) = env::var_os("MIRI_CWD") { |
| env::set_current_dir(cwd).unwrap(); |
| } |
| |
| // Emit warnings for some unusual configurations. |
| if tcx.sess.opts.optimize != OptLevel::No { |
| tcx.dcx().warn("Miri does not support optimizations: the opt-level is ignored. The only effect \ |
| of selecting a Cargo profile that enables optimizations (such as --release) is to apply \ |
| its remaining settings, such as whether debug assertions and overflow checks are enabled."); |
| } |
| if tcx.sess.mir_opt_level() > 0 { |
| tcx.dcx().warn("You have explicitly enabled MIR optimizations, overriding Miri's default \ |
| which is to completely disable them. Any optimizations may hide UB that Miri would \ |
| otherwise detect, and it is not necessarily possible to predict what kind of UB will \ |
| be missed. If you are enabling optimizations to make Miri run faster, we advise using \ |
| cfg(miri) to shrink your workload instead. The performance benefit of enabling MIR \ |
| optimizations is usually marginal at best."); |
| } |
| |
| // Invoke the interpreter. |
| let res = if config.genmc_config.is_some() { |
| assert!(self.many_seeds.is_none()); |
| run_genmc_mode(tcx, &config, |genmc_ctx: Rc<GenmcCtx>| { |
| miri::eval_entry(tcx, entry_def_id, entry_type, &config, Some(genmc_ctx)) |
| }) |
| } else if let Some(many_seeds) = self.many_seeds.take() { |
| assert!(config.seed.is_none()); |
| run_many_seeds(many_seeds, |seed| { |
| let mut config = config.clone(); |
| config.seed = Some(seed); |
| eprintln!("Trying seed: {seed}"); |
| miri::eval_entry(tcx, entry_def_id, entry_type, &config, /* genmc_ctx */ None) |
| }) |
| } else { |
| miri::eval_entry(tcx, entry_def_id, entry_type, &config, None) |
| }; |
| // Process interpreter result. |
| if let Err(return_code) = res { |
| tcx.dcx().abort_if_errors(); |
| exit(return_code.get()); |
| } else { |
| exit(rustc_driver::EXIT_SUCCESS); |
| } |
| |
| // Unreachable. |
| } |
| } |
| |
| /// This compiler produces rlibs that are meant for later consumption by Miri. |
| struct MiriDepCompilerCalls; |
| |
| impl rustc_driver::Callbacks for MiriDepCompilerCalls { |
| #[allow(rustc::potential_query_instability)] // rustc_codegen_ssa (where this code is copied from) also allows this lint |
| fn config(&mut self, config: &mut Config) { |
| // We don't need actual codegen, we just emit an rlib that Miri can later consume. |
| config.make_codegen_backend = Some(Box::new(make_miri_codegen_backend)); |
| |
| // Avoid warnings about unsupported crate types. However, only do that we we are *not* being |
| // queried by cargo about the supported crate types so that cargo still receives the |
| // warnings it expects. |
| if config.opts.prints.is_empty() { |
| #[allow(rustc::bad_opt_access)] // tcx does not exist yet |
| { |
| let any_crate_types = !config.opts.crate_types.is_empty(); |
| config |
| .opts |
| .crate_types |
| .retain(|&c| c == CrateType::Executable || c == CrateType::Rlib); |
| if any_crate_types { |
| // Assert that we didn't remove all crate types if any crate type was passed on |
| // the cli. Otherwise we might silently change what kind of crate we are building. |
| assert!(!config.opts.crate_types.is_empty()); |
| } |
| } |
| } |
| |
| // Queries overridden here affect the data stored in `rmeta` files of dependencies, |
| // which will be used later in non-`MIRI_BE_RUSTC` mode. |
| config.override_queries = Some(|_, local_providers| { |
| // We need to add #[used] symbols to exported_symbols for `lookup_link_section`. |
| // FIXME handle this somehow in rustc itself to avoid this hack. |
| local_providers.exported_non_generic_symbols = |tcx, LocalCrate| { |
| let reachable_set = tcx |
| .with_stable_hashing_context(|hcx| tcx.reachable_set(()).to_sorted(&hcx, true)); |
| tcx.arena.alloc_from_iter( |
| // This is based on: |
| // https://github.com/rust-lang/rust/blob/2962e7c0089d5c136f4e9600b7abccfbbde4973d/compiler/rustc_codegen_ssa/src/back/symbol_export.rs#L62-L63 |
| // https://github.com/rust-lang/rust/blob/2962e7c0089d5c136f4e9600b7abccfbbde4973d/compiler/rustc_codegen_ssa/src/back/symbol_export.rs#L174 |
| reachable_set.into_iter().filter_map(|&local_def_id| { |
| // Do the same filtering that rustc does: |
| // https://github.com/rust-lang/rust/blob/2962e7c0089d5c136f4e9600b7abccfbbde4973d/compiler/rustc_codegen_ssa/src/back/symbol_export.rs#L84-L102 |
| // Otherwise it may cause unexpected behaviours and ICEs |
| // (https://github.com/rust-lang/rust/issues/86261). |
| let is_reachable_non_generic = matches!( |
| tcx.hir_node_by_def_id(local_def_id), |
| Node::Item(&hir::Item { |
| kind: hir::ItemKind::Static(..) | hir::ItemKind::Fn{ .. }, |
| .. |
| }) | Node::ImplItem(&hir::ImplItem { |
| kind: hir::ImplItemKind::Fn(..), |
| .. |
| }) |
| if !tcx.generics_of(local_def_id).requires_monomorphization(tcx) |
| ); |
| if !is_reachable_non_generic { |
| return None; |
| } |
| let codegen_fn_attrs = tcx.codegen_fn_attrs(local_def_id); |
| if codegen_fn_attrs.contains_extern_indicator() |
| || codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::USED_COMPILER) |
| || codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::USED_LINKER) |
| { |
| Some(( |
| ExportedSymbol::NonGeneric(local_def_id.to_def_id()), |
| // Some dummy `SymbolExportInfo` here. We only use |
| // `exported_symbols` in shims/foreign_items.rs and the export info |
| // is ignored. |
| SymbolExportInfo { |
| level: SymbolExportLevel::C, |
| kind: SymbolExportKind::Text, |
| used: false, |
| rustc_std_internal_symbol: false, |
| }, |
| )) |
| } else { |
| None |
| } |
| }), |
| ) |
| } |
| }); |
| } |
| |
| fn after_analysis<'tcx>( |
| &mut self, |
| _: &rustc_interface::interface::Compiler, |
| tcx: TyCtxt<'tcx>, |
| ) -> Compilation { |
| // While the dummy codegen backend doesn't do any codegen, we are still emulating |
| // regular rustc builds, which would perform post-mono const-eval during collection. |
| // So let's also do that here. In particular this is needed to make `compile_fail` |
| // doc tests trigger post-mono errors. |
| let _ = tcx.collect_and_partition_mono_items(()); |
| |
| Compilation::Continue |
| } |
| } |
| |
| fn exit(exit_code: i32) -> ! { |
| // Drop the tracing guard before exiting, so tracing calls are flushed correctly. |
| deinit_loggers(); |
| // Make sure the supervisor knows about the exit code. |
| #[cfg(all(unix, feature = "native-lib"))] |
| miri::native_lib::register_retcode_sv(exit_code); |
| // Actually exit. |
| std::process::exit(exit_code); |
| } |
| |
| fn fatal_error_(msg: &impl std::fmt::Display) -> ! { |
| eprintln!("fatal error: {msg}"); |
| exit(1) |
| } |
| |
| macro_rules! fatal_error { |
| ($($tt:tt)*) => { $crate::fatal_error_(&format_args!($($tt)*)) }; |
| } |
| #[allow(unused)] // use depends on cfg |
| use fatal_error; |
| |
| /// Execute a compiler with the given CLI arguments and callbacks. |
| fn run_compiler_and_exit( |
| args: &[String], |
| callbacks: &mut (dyn rustc_driver::Callbacks + Send), |
| ) -> ! { |
| // Install the ctrlc handler that sets `rustc_const_eval::CTRL_C_RECEIVED`, even if |
| // MIRI_BE_RUSTC is set. We do this late so that when `native_lib::init_sv` is called, |
| // there are no other threads. |
| rustc_driver::install_ctrlc_handler(); |
| |
| // Invoke compiler, catch any unwinding panics and handle return code. |
| let exit_code = |
| rustc_driver::catch_with_exit_code(move || rustc_driver::run_compiler(args, callbacks)); |
| exit(exit_code) |
| } |
| |
| /// Parses a comma separated list of `T` from the given string: |
| /// `<value1>,<value2>,<value3>,...` |
| fn parse_comma_list<T: FromStr>(input: &str) -> Result<Vec<T>, T::Err> { |
| input.split(',').map(str::parse::<T>).collect() |
| } |
| |
| /// Parses the input as a float in the range from 0.0 to 1.0 (inclusive). |
| fn parse_rate(input: &str) -> Result<f64, &'static str> { |
| match input.parse::<f64>() { |
| Ok(rate) if rate >= 0.0 && rate <= 1.0 => Ok(rate), |
| Ok(_) => Err("must be between `0.0` and `1.0`"), |
| Err(_) => Err("requires a `f64` between `0.0` and `1.0`"), |
| } |
| } |
| |
| /// 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) |
| } |
| |
| fn main() { |
| let early_dcx = EarlyDiagCtxt::new(ErrorOutputType::default()); |
| |
| // Snapshot a copy of the environment before `rustc` starts messing with it. |
| // (`install_ice_hook` might change `RUST_BACKTRACE`.) |
| let env_snapshot = env::vars_os().collect::<Vec<_>>(); |
| |
| let args = rustc_driver::catch_fatal_errors(|| rustc_driver::args::raw_args(&early_dcx)) |
| .unwrap_or_else(|_| std::process::exit(rustc_driver::EXIT_FAILURE)); |
| |
| // If the environment asks us to actually be rustc, then do that. |
| if let Some(crate_kind) = env::var_os("MIRI_BE_RUSTC") { |
| if crate_kind == "host" { |
| // For host crates like proc macros and build scripts, we are an entirely normal rustc. |
| // These eventually produce actual binaries and never run in Miri. |
| match rustc_driver::main() { |
| // Empty match proves this function will never return. |
| } |
| } else if crate_kind != "target" { |
| panic!("invalid `MIRI_BE_RUSTC` value: {crate_kind:?}") |
| }; |
| |
| // Earliest rustc setup. |
| rustc_driver::install_ice_hook(rustc_driver::DEFAULT_BUG_REPORT_URL, |_| ()); |
| rustc_driver::init_rustc_env_logger(&early_dcx); |
| |
| let mut args = args; |
| // 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 MiriDepCompilerCalls) |
| } |
| |
| // Add an ICE bug report hook. |
| rustc_driver::install_ice_hook("https://github.com/rust-lang/miri/issues/new", |_| ()); |
| |
| // Init loggers the Miri way. |
| init_early_loggers(&early_dcx); |
| |
| // Parse our arguments and split them across `rustc` and `miri`. |
| let mut many_seeds: Option<Range<u32>> = None; |
| let mut many_seeds_keep_going = false; |
| let mut miri_config = MiriConfig::default(); |
| miri_config.env = env_snapshot; |
| |
| let mut rustc_args = vec![]; |
| let mut after_dashdash = false; |
| |
| // Note that we require values to be given with `=`, not with a space. |
| // This matches how rustc parses `-Z`. |
| // However, unlike rustc we do not accept a space after `-Z`. |
| for arg in args { |
| 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); |
| } else if arg == "--" { |
| after_dashdash = true; |
| } else if arg == "-Zmiri-disable-validation" { |
| miri_config.validation = ValidationMode::No; |
| } else if arg == "-Zmiri-recursive-validation" { |
| miri_config.validation = ValidationMode::Deep; |
| } else if arg == "-Zmiri-disable-stacked-borrows" { |
| miri_config.borrow_tracker = None; |
| } else if arg == "-Zmiri-tree-borrows" { |
| miri_config.borrow_tracker = |
| Some(BorrowTrackerMethod::TreeBorrows(TreeBorrowsParams { |
| precise_interior_mut: true, |
| })); |
| } else if arg == "-Zmiri-tree-borrows-no-precise-interior-mut" { |
| match &mut miri_config.borrow_tracker { |
| Some(BorrowTrackerMethod::TreeBorrows(params)) => { |
| params.precise_interior_mut = false; |
| } |
| _ => |
| fatal_error!( |
| "`-Zmiri-tree-borrows` is required before `-Zmiri-tree-borrows-no-precise-interior-mut`" |
| ), |
| }; |
| } else if arg == "-Zmiri-disable-data-race-detector" { |
| miri_config.data_race_detector = false; |
| miri_config.weak_memory_emulation = false; |
| } else if arg == "-Zmiri-disable-alignment-check" { |
| miri_config.check_alignment = miri::AlignmentCheck::None; |
| } else if arg == "-Zmiri-symbolic-alignment-check" { |
| miri_config.check_alignment = miri::AlignmentCheck::Symbolic; |
| } else if arg == "-Zmiri-disable-isolation" { |
| miri_config.isolated_op = miri::IsolatedOp::Allow; |
| } else if arg == "-Zmiri-disable-leak-backtraces" { |
| miri_config.collect_leak_backtraces = false; |
| } else if arg == "-Zmiri-disable-weak-memory-emulation" { |
| miri_config.weak_memory_emulation = false; |
| } else if arg == "-Zmiri-track-weak-memory-loads" { |
| miri_config.track_outdated_loads = true; |
| } else if let Some(param) = arg.strip_prefix("-Zmiri-isolation-error=") { |
| miri_config.isolated_op = match param { |
| "abort" => miri::IsolatedOp::Reject(miri::RejectOpWith::Abort), |
| "hide" => miri::IsolatedOp::Reject(miri::RejectOpWith::NoWarning), |
| "warn" => miri::IsolatedOp::Reject(miri::RejectOpWith::Warning), |
| "warn-nobacktrace" => |
| miri::IsolatedOp::Reject(miri::RejectOpWith::WarningWithoutBacktrace), |
| _ => |
| fatal_error!( |
| "-Zmiri-isolation-error must be `abort`, `hide`, `warn`, or `warn-nobacktrace`" |
| ), |
| }; |
| } else if arg == "-Zmiri-ignore-leaks" { |
| miri_config.ignore_leaks = true; |
| miri_config.collect_leak_backtraces = false; |
| } else if arg == "-Zmiri-force-intrinsic-fallback" { |
| miri_config.force_intrinsic_fallback = true; |
| } else if arg == "-Zmiri-deterministic-floats" { |
| miri_config.float_nondet = false; |
| } else if arg == "-Zmiri-no-extra-rounding-error" { |
| miri_config.float_rounding_error = miri::FloatRoundingErrorMode::None; |
| } else if arg == "-Zmiri-max-extra-rounding-error" { |
| miri_config.float_rounding_error = miri::FloatRoundingErrorMode::Max; |
| } else if arg == "-Zmiri-no-short-fd-operations" { |
| miri_config.short_fd_operations = false; |
| } else if arg == "-Zmiri-strict-provenance" { |
| miri_config.provenance_mode = ProvenanceMode::Strict; |
| } else if arg == "-Zmiri-permissive-provenance" { |
| miri_config.provenance_mode = ProvenanceMode::Permissive; |
| } else if arg == "-Zmiri-mute-stdout-stderr" { |
| miri_config.mute_stdout_stderr = true; |
| } else if arg == "-Zmiri-retag-fields" { |
| eprintln!( |
| "warning: `-Zmiri-retag-fields` is a NOP and will be removed in a future version of Miri.\n\ |
| Field retagging has been on-by-default for a long time." |
| ); |
| } else if arg == "-Zmiri-fixed-schedule" { |
| miri_config.fixed_scheduling = true; |
| } else if arg == "-Zmiri-deterministic-concurrency" { |
| miri_config.fixed_scheduling = true; |
| miri_config.address_reuse_cross_thread_rate = 0.0; |
| miri_config.cmpxchg_weak_failure_rate = 0.0; |
| miri_config.weak_memory_emulation = false; |
| } else if let Some(param) = arg.strip_prefix("-Zmiri-seed=") { |
| let seed = param.parse::<u64>().unwrap_or_else(|_| { |
| fatal_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| { |
| fatal_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 arg == "-Zmiri-many-seeds-keep-going" { |
| many_seeds_keep_going = true; |
| } else if let Some(trimmed_arg) = arg.strip_prefix("-Zmiri-genmc") { |
| if let Err(msg) = GenmcConfig::parse_arg(&mut miri_config.genmc_config, trimmed_arg) { |
| fatal_error!("{msg}"); |
| } |
| } else if let Some(param) = arg.strip_prefix("-Zmiri-env-forward=") { |
| miri_config.forwarded_env_vars.push(param.to_owned()); |
| } else if let Some(param) = arg.strip_prefix("-Zmiri-env-set=") { |
| let Some((name, value)) = param.split_once('=') else { |
| fatal_error!("-Zmiri-env-set requires an argument of the form <name>=<value>"); |
| }; |
| miri_config.set_env_vars.insert(name.to_owned(), value.to_owned()); |
| } else if let Some(param) = arg.strip_prefix("-Zmiri-track-pointer-tag=") { |
| let ids: Vec<u64> = parse_comma_list(param).unwrap_or_else(|err| { |
| fatal_error!("-Zmiri-track-pointer-tag requires a comma separated list of valid `u64` arguments: {err}") |
| }); |
| for id in ids.into_iter().map(miri::BorTag::new) { |
| if let Some(id) = id { |
| miri_config.tracked_pointer_tags.insert(id); |
| } else { |
| fatal_error!("-Zmiri-track-pointer-tag requires nonzero arguments"); |
| } |
| } |
| } else if let Some(param) = arg.strip_prefix("-Zmiri-track-alloc-id=") { |
| let ids = parse_comma_list::<NonZero<u64>>(param).unwrap_or_else(|err| { |
| fatal_error!("-Zmiri-track-alloc-id requires a comma separated list of valid non-zero `u64` arguments: {err}") |
| }); |
| miri_config.tracked_alloc_ids.extend(ids.into_iter().map(miri::AllocId)); |
| } else if arg == "-Zmiri-track-alloc-accesses" { |
| miri_config.track_alloc_accesses = true; |
| } else if let Some(param) = arg.strip_prefix("-Zmiri-address-reuse-rate=") { |
| miri_config.address_reuse_rate = parse_rate(param) |
| .unwrap_or_else(|err| fatal_error!("-Zmiri-address-reuse-rate {err}")); |
| } else if let Some(param) = arg.strip_prefix("-Zmiri-address-reuse-cross-thread-rate=") { |
| miri_config.address_reuse_cross_thread_rate = parse_rate(param) |
| .unwrap_or_else(|err| fatal_error!("-Zmiri-address-reuse-cross-thread-rate {err}")); |
| } else if let Some(param) = arg.strip_prefix("-Zmiri-compare-exchange-weak-failure-rate=") { |
| miri_config.cmpxchg_weak_failure_rate = parse_rate(param).unwrap_or_else(|err| { |
| fatal_error!("-Zmiri-compare-exchange-weak-failure-rate {err}") |
| }); |
| } else if let Some(param) = arg.strip_prefix("-Zmiri-preemption-rate=") { |
| miri_config.preemption_rate = parse_rate(param) |
| .unwrap_or_else(|err| fatal_error!("-Zmiri-preemption-rate {err}")); |
| } else if arg == "-Zmiri-report-progress" { |
| // This makes it take a few seconds between progress reports on my laptop. |
| miri_config.report_progress = Some(1_000_000); |
| } else if let Some(param) = arg.strip_prefix("-Zmiri-report-progress=") { |
| let interval = param.parse::<u32>().unwrap_or_else(|err| { |
| fatal_error!("-Zmiri-report-progress requires a `u32`: {}", err) |
| }); |
| miri_config.report_progress = Some(interval); |
| } else if let Some(param) = arg.strip_prefix("-Zmiri-provenance-gc=") { |
| let interval = param.parse::<u32>().unwrap_or_else(|err| { |
| fatal_error!("-Zmiri-provenance-gc requires a `u32`: {}", err) |
| }); |
| miri_config.gc_interval = interval; |
| } else if let Some(param) = arg.strip_prefix("-Zmiri-measureme=") { |
| miri_config.measureme_out = Some(param.to_string()); |
| } else if let Some(param) = arg.strip_prefix("-Zmiri-backtrace=") { |
| miri_config.backtrace_style = match param { |
| "0" => BacktraceStyle::Off, |
| "1" => BacktraceStyle::Short, |
| "full" => BacktraceStyle::Full, |
| _ => fatal_error!("-Zmiri-backtrace may only be 0, 1, or full"), |
| }; |
| } else if let Some(param) = arg.strip_prefix("-Zmiri-native-lib=") { |
| let filename = param.to_string(); |
| let file_path = std::path::Path::new(&filename); |
| if file_path.exists() { |
| // For directories, nonrecursively add all normal files inside |
| if let Ok(dir) = file_path.read_dir() { |
| for lib in dir.filter_map(|res| res.ok()) { |
| if lib.file_type().unwrap().is_file() { |
| miri_config.native_lib.push(lib.path().to_owned()); |
| } |
| } |
| } else { |
| miri_config.native_lib.push(filename.into()); |
| } |
| } else { |
| fatal_error!("-Zmiri-native-lib `{}` does not exist", filename); |
| } |
| } else if arg == "-Zmiri-native-lib-enable-tracing" { |
| miri_config.native_lib_enable_tracing = true; |
| } else if let Some(param) = arg.strip_prefix("-Zmiri-num-cpus=") { |
| let num_cpus = param |
| .parse::<u32>() |
| .unwrap_or_else(|err| fatal_error!("-Zmiri-num-cpus requires a `u32`: {}", err)); |
| if !(1..=miri::MAX_CPUS).contains(&usize::try_from(num_cpus).unwrap()) { |
| fatal_error!("-Zmiri-num-cpus must be in the range 1..={}", miri::MAX_CPUS); |
| } |
| miri_config.num_cpus = num_cpus; |
| } else if let Some(param) = arg.strip_prefix("-Zmiri-force-page-size=") { |
| let page_size = param.parse::<u64>().unwrap_or_else(|err| { |
| fatal_error!("-Zmiri-force-page-size requires a `u64`: {}", err) |
| }); |
| // Convert from kilobytes to bytes. |
| let page_size = if page_size.is_power_of_two() { |
| page_size * 1024 |
| } else { |
| fatal_error!("-Zmiri-force-page-size requires a power of 2: {page_size}"); |
| }; |
| miri_config.page_size = Some(page_size); |
| } else if let Some(param) = arg.strip_prefix("-Zmiri-user-relevant-crates=") { |
| miri_config.user_relevant_crates.extend(param.split(',').map(|s| s.to_owned())); |
| } else { |
| // Forward to rustc. |
| rustc_args.push(arg); |
| } |
| } |
| |
| // Native calls and strict provenance are not compatible. |
| if !miri_config.native_lib.is_empty() && miri_config.provenance_mode == ProvenanceMode::Strict { |
| fatal_error!("strict provenance is not compatible with calling native functions"); |
| } |
| // Native calls and many-seeds are an "intersting" combination. |
| if !miri_config.native_lib.is_empty() && many_seeds.is_some() { |
| eprintln!( |
| "warning: `-Zmiri-many-seeds` runs multiple instances of the program in the same address space, \ |
| so if the native library has global state, it will leak across execution bundaries" |
| ); |
| } |
| // You can set either one seed or many. |
| if many_seeds.is_some() && miri_config.seed.is_some() { |
| fatal_error!("Only one of `-Zmiri-seed` and `-Zmiri-many-seeds can be set"); |
| } |
| // We cannot emulate weak memory without the data race detector. |
| if miri_config.weak_memory_emulation && !miri_config.data_race_detector { |
| fatal_error!( |
| "Weak memory emulation cannot be enabled when the data race detector is disabled" |
| ); |
| }; |
| |
| // Validate GenMC settings. |
| if miri_config.genmc_config.is_some() |
| && let Err(err) = GenmcConfig::validate(&mut miri_config) |
| { |
| fatal_error!("Invalid settings: {err}"); |
| } |
| |
| // Ensure we have parallelism for many-seeds mode. |
| if many_seeds.is_some() && !rustc_args.iter().any(|arg| arg.starts_with("-Zthreads=")) { |
| // Clamp to 20 threads; things get a less efficient beyond that due to lock contention. |
| let threads = std::thread::available_parallelism().map_or(1, |n| n.get()).min(20); |
| rustc_args.push(format!("-Zthreads={threads}")); |
| } |
| let many_seeds = |
| many_seeds.map(|seeds| ManySeedsConfig { seeds, keep_going: many_seeds_keep_going }); |
| |
| debug!("rustc arguments: {:?}", rustc_args); |
| debug!("crate arguments: {:?}", miri_config.args); |
| if !miri_config.native_lib.is_empty() && miri_config.native_lib_enable_tracing { |
| // SAFETY: No other threads are running |
| #[cfg(all(unix, feature = "native-lib"))] |
| if unsafe { miri::native_lib::init_sv() }.is_err() { |
| eprintln!( |
| "warning: The native-lib tracer could not be started. Is this an x86 Linux system, and does Miri have permissions to ptrace?\n\ |
| Falling back to non-tracing native-lib mode." |
| ); |
| } |
| } |
| run_compiler_and_exit(&rustc_args, &mut MiriCompilerCalls::new(miri_config, many_seeds)) |
| } |