11//! Implements the various phases of `cargo miri run/test`.
22
3- use std:: env;
43use std:: fs:: { self , File } ;
5- use std:: io:: BufReader ;
4+ use std:: io:: { BufReader , Write } ;
65use std:: path:: { Path , PathBuf } ;
76use std:: process:: Command ;
7+ use std:: { env, thread} ;
88
99use rustc_version:: VersionMeta ;
1010
@@ -119,7 +119,7 @@ pub fn phase_cargo_miri(mut args: impl Iterator<Item = String>) {
119119 // <https://github.com/rust-lang/miri/pull/1540#issuecomment-693553191> describes an alternative
120120 // approach that uses `cargo check`, making that part easier but target and binary handling
121121 // harder.
122- let cargo_miri_path = std :: env:: current_exe ( )
122+ let cargo_miri_path = env:: current_exe ( )
123123 . expect ( "current executable path invalid" )
124124 . into_os_string ( )
125125 . into_string ( )
@@ -163,14 +163,22 @@ pub fn phase_cargo_miri(mut args: impl Iterator<Item = String>) {
163163 let target_dir = get_target_dir ( & metadata) ;
164164 cmd. arg ( "--target-dir" ) . arg ( target_dir) ;
165165
166+ // Store many-seeds argument.
167+ let mut many_seeds = None ;
166168 // *After* we set all the flags that need setting, forward everything else. Make sure to skip
167- // `--target-dir` (which would otherwise be set twice).
169+ // `--target-dir` (which would otherwise be set twice) and `--many-seeds` (which is our flag, not cargo's) .
168170 for arg in
169171 ArgSplitFlagValue :: from_string_iter ( & mut args, "--target-dir" ) . filter_map ( Result :: err)
170172 {
171- cmd. arg ( arg) ;
173+ if arg == "--many-seeds" {
174+ many_seeds = Some ( format ! ( "0..256" ) ) ;
175+ } else if let Some ( val) = arg. strip_prefix ( "--many-seeds=" ) {
176+ many_seeds = Some ( val. to_owned ( ) ) ;
177+ } else {
178+ cmd. arg ( arg) ;
179+ }
172180 }
173- // Forward all further arguments (not consumed by `ArgSplitFlagValue`) to cargo.
181+ // Forward all further arguments after `--` (not consumed by `ArgSplitFlagValue`) to cargo.
174182 cmd. args ( args) ;
175183
176184 // Set `RUSTC_WRAPPER` to ourselves. Cargo will prepend that binary to its usual invocation,
@@ -222,6 +230,9 @@ pub fn phase_cargo_miri(mut args: impl Iterator<Item = String>) {
222230 // Forward some crucial information to our own re-invocations.
223231 cmd. env ( "MIRI_SYSROOT" , miri_sysroot) ;
224232 cmd. env ( "MIRI_LOCAL_CRATES" , local_crates ( & metadata) ) ;
233+ if let Some ( many_seeds) = many_seeds {
234+ cmd. env ( "MIRI_MANY_SEEDS" , many_seeds) ;
235+ }
225236 if verbose > 0 {
226237 cmd. env ( "MIRI_VERBOSE" , verbose. to_string ( ) ) ; // This makes the other phases verbose.
227238 }
@@ -309,7 +320,7 @@ pub fn phase_rustc(mut args: impl Iterator<Item = String>, phase: RustcPhase) {
309320 }
310321 }
311322
312- let verbose = std :: env:: var ( "MIRI_VERBOSE" )
323+ let verbose = env:: var ( "MIRI_VERBOSE" )
313324 . map_or ( 0 , |verbose| verbose. parse ( ) . expect ( "verbosity flag must be an integer" ) ) ;
314325 let target_crate = is_target_crate ( ) ;
315326
@@ -489,7 +500,7 @@ pub fn phase_rustc(mut args: impl Iterator<Item = String>, phase: RustcPhase) {
489500 // This is a host crate.
490501 // When we're running `cargo-miri` from `x.py` we need to pass the sysroot explicitly
491502 // due to bootstrap complications.
492- if let Some ( sysroot) = std :: env:: var_os ( "MIRI_HOST_SYSROOT" ) {
503+ if let Some ( sysroot) = env:: var_os ( "MIRI_HOST_SYSROOT" ) {
493504 cmd. arg ( "--sysroot" ) . arg ( sysroot) ;
494505 }
495506
@@ -532,7 +543,7 @@ pub enum RunnerPhase {
532543}
533544
534545pub fn phase_runner ( mut binary_args : impl Iterator < Item = String > , phase : RunnerPhase ) {
535- let verbose = std :: env:: var ( "MIRI_VERBOSE" )
546+ let verbose = env:: var ( "MIRI_VERBOSE" )
536547 . map_or ( 0 , |verbose| verbose. parse ( ) . expect ( "verbosity flag must be an integer" ) ) ;
537548
538549 let binary = binary_args. next ( ) . unwrap ( ) ;
@@ -541,6 +552,7 @@ pub fn phase_runner(mut binary_args: impl Iterator<Item = String>, phase: Runner
541552 "file {:?} not found or `cargo-miri` invoked incorrectly; please only invoke this binary through `cargo miri`" , binary
542553 ) ) ;
543554 let file = BufReader :: new ( file) ;
555+ let binary_args = binary_args. collect :: < Vec < _ > > ( ) ;
544556
545557 let info = serde_json:: from_reader ( file) . unwrap_or_else ( |_| {
546558 show_error ! ( "file {:?} contains outdated or invalid JSON; try `cargo clean`" , binary)
@@ -555,84 +567,114 @@ pub fn phase_runner(mut binary_args: impl Iterator<Item = String>, phase: Runner
555567 }
556568 } ;
557569
558- let mut cmd = miri ( ) ;
559-
560- // Set missing env vars. We prefer build-time env vars over run-time ones; see
561- // <https://github.com/rust-lang/miri/issues/1661> for the kind of issue that fixes.
562- for ( name, val) in info. env {
563- // `CARGO_MAKEFLAGS` contains information about how to reach the jobserver, but by the time
564- // the program is being run, that jobserver no longer exists (cargo only runs the jobserver
565- // for the build portion of `cargo run`/`cargo test`). Hence we shouldn't forward this.
566- // Also see <https://github.com/rust-lang/rust/pull/113730>.
567- if name == "CARGO_MAKEFLAGS" {
568- continue ;
569- }
570- if let Some ( old_val) = env:: var_os ( & name) {
571- if old_val == val {
572- // This one did not actually change, no need to re-set it.
573- // (This keeps the `debug_cmd` below more manageable.)
570+ let many_seeds = env:: var ( "MIRI_MANY_SEEDS" ) ;
571+ run_many_seeds ( many_seeds. ok ( ) , |seed| {
572+ let mut cmd = miri ( ) ;
573+
574+ // Set missing env vars. We prefer build-time env vars over run-time ones; see
575+ // <https://github.com/rust-lang/miri/issues/1661> for the kind of issue that fixes.
576+ for ( name, val) in & info. env {
577+ // `CARGO_MAKEFLAGS` contains information about how to reach the jobserver, but by the time
578+ // the program is being run, that jobserver no longer exists (cargo only runs the jobserver
579+ // for the build portion of `cargo run`/`cargo test`). Hence we shouldn't forward this.
580+ // Also see <https://github.com/rust-lang/rust/pull/113730>.
581+ if name == "CARGO_MAKEFLAGS" {
574582 continue ;
575- } else if verbose > 0 {
576- eprintln ! (
577- "[cargo-miri runner] Overwriting run-time env var {name:?}={old_val:?} with build-time value {val:?}"
578- ) ;
579583 }
584+ if let Some ( old_val) = env:: var_os ( name) {
585+ if * old_val == * val {
586+ // This one did not actually change, no need to re-set it.
587+ // (This keeps the `debug_cmd` below more manageable.)
588+ continue ;
589+ } else if verbose > 0 {
590+ eprintln ! (
591+ "[cargo-miri runner] Overwriting run-time env var {name:?}={old_val:?} with build-time value {val:?}"
592+ ) ;
593+ }
594+ }
595+ cmd. env ( name, val) ;
580596 }
581- cmd. env ( name, val) ;
582- }
583597
584- if phase != RunnerPhase :: Rustdoc {
585- // Set the sysroot. Not necessary in rustdoc, where we already set the sysroot in
586- // `phase_rustdoc`. rustdoc will forward that flag when invoking rustc (i.e., us), so the
587- // flag is present in `info.args`.
588- cmd. arg ( "--sysroot" ) . arg ( env:: var_os ( "MIRI_SYSROOT" ) . unwrap ( ) ) ;
589- }
590- // Forward rustc arguments.
591- // We need to patch "--extern" filenames because we forced a check-only
592- // build without cargo knowing about that: replace `.rlib` suffix by
593- // `.rmeta`.
594- // We also need to remove `--error-format` as cargo specifies that to be JSON,
595- // but when we run here, cargo does not interpret the JSON any more. `--json`
596- // then also needs to be dropped.
597- let mut args = info. args . into_iter ( ) ;
598- while let Some ( arg) = args. next ( ) {
599- if arg == "--extern" {
600- forward_patched_extern_arg ( & mut args, & mut cmd) ;
601- } else if let Some ( suffix) = arg. strip_prefix ( "--error-format" ) {
602- assert ! ( suffix. starts_with( '=' ) ) ;
603- // Drop this argument.
604- } else if let Some ( suffix) = arg. strip_prefix ( "--json" ) {
605- assert ! ( suffix. starts_with( '=' ) ) ;
606- // Drop this argument.
607- } else {
608- cmd. arg ( arg) ;
598+ if phase != RunnerPhase :: Rustdoc {
599+ // Set the sysroot. Not necessary in rustdoc, where we already set the sysroot in
600+ // `phase_rustdoc`. rustdoc will forward that flag when invoking rustc (i.e., us), so the
601+ // flag is present in `info.args`.
602+ cmd. arg ( "--sysroot" ) . arg ( env:: var_os ( "MIRI_SYSROOT" ) . unwrap ( ) ) ;
603+ }
604+ // Forward rustc arguments.
605+ // We need to patch "--extern" filenames because we forced a check-only
606+ // build without cargo knowing about that: replace `.rlib` suffix by
607+ // `.rmeta`.
608+ // We also need to remove `--error-format` as cargo specifies that to be JSON,
609+ // but when we run here, cargo does not interpret the JSON any more. `--json`
610+ // then also needs to be dropped.
611+ let mut args = info. args . iter ( ) ;
612+ while let Some ( arg) = args. next ( ) {
613+ if arg == "--extern" {
614+ forward_patched_extern_arg ( & mut ( & mut args) . cloned ( ) , & mut cmd) ;
615+ } else if let Some ( suffix) = arg. strip_prefix ( "--error-format" ) {
616+ assert ! ( suffix. starts_with( '=' ) ) ;
617+ // Drop this argument.
618+ } else if let Some ( suffix) = arg. strip_prefix ( "--json" ) {
619+ assert ! ( suffix. starts_with( '=' ) ) ;
620+ // Drop this argument.
621+ } else {
622+ cmd. arg ( arg) ;
623+ }
624+ }
625+ // Respect `MIRIFLAGS`.
626+ if let Ok ( a) = env:: var ( "MIRIFLAGS" ) {
627+ let args = flagsplit ( & a) ;
628+ cmd. args ( args) ;
629+ }
630+ // Set the current seed.
631+ if let Some ( seed) = seed {
632+ eprintln ! ( "Trying seed: {seed}" ) ;
633+ cmd. arg ( format ! ( "-Zmiri-seed={seed}" ) ) ;
609634 }
610- }
611- // Respect `MIRIFLAGS`.
612- if let Ok ( a) = env:: var ( "MIRIFLAGS" ) {
613- let args = flagsplit ( & a) ;
614- cmd. args ( args) ;
615- }
616-
617- // Then pass binary arguments.
618- cmd. arg ( "--" ) ;
619- cmd. args ( binary_args) ;
620-
621- // Make sure we use the build-time working directory for interpreting Miri/rustc arguments.
622- // But then we need to switch to the run-time one, which we instruct Miri to do by setting `MIRI_CWD`.
623- cmd. current_dir ( info. current_dir ) ;
624- cmd. env ( "MIRI_CWD" , env:: current_dir ( ) . unwrap ( ) ) ;
625635
626- // Run it.
627- debug_cmd ( "[cargo-miri runner]" , verbose, & cmd) ;
628- match phase {
629- RunnerPhase :: Rustdoc => exec_with_pipe ( cmd, & info. stdin , format ! ( "{binary}.stdin" ) ) ,
630- RunnerPhase :: Cargo => exec ( cmd) ,
631- }
636+ // Then pass binary arguments.
637+ cmd. arg ( "--" ) ;
638+ cmd. args ( & binary_args) ;
639+
640+ // Make sure we use the build-time working directory for interpreting Miri/rustc arguments.
641+ // But then we need to switch to the run-time one, which we instruct Miri to do by setting `MIRI_CWD`.
642+ cmd. current_dir ( & info. current_dir ) ;
643+ cmd. env ( "MIRI_CWD" , env:: current_dir ( ) . unwrap ( ) ) ;
644+
645+ // Run it.
646+ debug_cmd ( "[cargo-miri runner]" , verbose, & cmd) ;
647+
648+ match phase {
649+ RunnerPhase :: Rustdoc => {
650+ cmd. stdin ( std:: process:: Stdio :: piped ( ) ) ;
651+ let mut child = cmd. spawn ( ) . expect ( "failed to spawn process" ) ;
652+ let child_stdin = child. stdin . take ( ) . unwrap ( ) ;
653+ // Write stdin in a background thread, as it may block.
654+ let exit_status = thread:: scope ( |s| {
655+ s. spawn ( || {
656+ let mut child_stdin = child_stdin;
657+ // Ignore failure, it is most likely due to the process having terminated.
658+ let _ = child_stdin. write_all ( & info. stdin ) ;
659+ } ) ;
660+ child. wait ( ) . expect ( "failed to run command" )
661+ } ) ;
662+ if !exit_status. success ( ) {
663+ std:: process:: exit ( exit_status. code ( ) . unwrap_or ( -1 ) ) ;
664+ }
665+ }
666+ RunnerPhase :: Cargo => {
667+ let exit_status = cmd. status ( ) . expect ( "failed to run command" ) ;
668+ if !exit_status. success ( ) {
669+ std:: process:: exit ( exit_status. code ( ) . unwrap_or ( -1 ) ) ;
670+ }
671+ }
672+ }
673+ } ) ;
632674}
633675
634676pub fn phase_rustdoc ( mut args : impl Iterator < Item = String > ) {
635- let verbose = std :: env:: var ( "MIRI_VERBOSE" )
677+ let verbose = env:: var ( "MIRI_VERBOSE" )
636678 . map_or ( 0 , |verbose| verbose. parse ( ) . expect ( "verbosity flag must be an integer" ) ) ;
637679
638680 // phase_cargo_miri sets the RUSTDOC env var to ourselves, and puts a backup
@@ -681,7 +723,7 @@ pub fn phase_rustdoc(mut args: impl Iterator<Item = String>) {
681723 cmd. arg ( "--cfg" ) . arg ( "miri" ) ;
682724
683725 // Make rustdoc call us back.
684- let cargo_miri_path = std :: env:: current_exe ( ) . expect ( "current executable path invalid" ) ;
726+ let cargo_miri_path = env:: current_exe ( ) . expect ( "current executable path invalid" ) ;
685727 cmd. arg ( "--test-builder" ) . arg ( & cargo_miri_path) ; // invoked by forwarding most arguments
686728 cmd. arg ( "--runtool" ) . arg ( & cargo_miri_path) ; // invoked with just a single path argument
687729
0 commit comments