1
1
//! Implements the various phases of `cargo miri run/test`.
2
2
3
- use std:: env;
4
3
use std:: fs:: { self , File } ;
5
- use std:: io:: BufReader ;
4
+ use std:: io:: { BufReader , Write } ;
6
5
use std:: path:: { Path , PathBuf } ;
7
6
use std:: process:: Command ;
7
+ use std:: { env, thread} ;
8
8
9
9
use rustc_version:: VersionMeta ;
10
10
@@ -119,7 +119,7 @@ pub fn phase_cargo_miri(mut args: impl Iterator<Item = String>) {
119
119
// <https://github.com/rust-lang/miri/pull/1540#issuecomment-693553191> describes an alternative
120
120
// approach that uses `cargo check`, making that part easier but target and binary handling
121
121
// harder.
122
- let cargo_miri_path = std :: env:: current_exe ( )
122
+ let cargo_miri_path = env:: current_exe ( )
123
123
. expect ( "current executable path invalid" )
124
124
. into_os_string ( )
125
125
. into_string ( )
@@ -163,14 +163,22 @@ pub fn phase_cargo_miri(mut args: impl Iterator<Item = String>) {
163
163
let target_dir = get_target_dir ( & metadata) ;
164
164
cmd. arg ( "--target-dir" ) . arg ( target_dir) ;
165
165
166
+ // Store many-seeds argument.
167
+ let mut many_seeds = None ;
166
168
// *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) .
168
170
for arg in
169
171
ArgSplitFlagValue :: from_string_iter ( & mut args, "--target-dir" ) . filter_map ( Result :: err)
170
172
{
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
+ }
172
180
}
173
- // Forward all further arguments (not consumed by `ArgSplitFlagValue`) to cargo.
181
+ // Forward all further arguments after `--` (not consumed by `ArgSplitFlagValue`) to cargo.
174
182
cmd. args ( args) ;
175
183
176
184
// 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>) {
222
230
// Forward some crucial information to our own re-invocations.
223
231
cmd. env ( "MIRI_SYSROOT" , miri_sysroot) ;
224
232
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
+ }
225
236
if verbose > 0 {
226
237
cmd. env ( "MIRI_VERBOSE" , verbose. to_string ( ) ) ; // This makes the other phases verbose.
227
238
}
@@ -309,7 +320,7 @@ pub fn phase_rustc(mut args: impl Iterator<Item = String>, phase: RustcPhase) {
309
320
}
310
321
}
311
322
312
- let verbose = std :: env:: var ( "MIRI_VERBOSE" )
323
+ let verbose = env:: var ( "MIRI_VERBOSE" )
313
324
. map_or ( 0 , |verbose| verbose. parse ( ) . expect ( "verbosity flag must be an integer" ) ) ;
314
325
let target_crate = is_target_crate ( ) ;
315
326
@@ -489,7 +500,7 @@ pub fn phase_rustc(mut args: impl Iterator<Item = String>, phase: RustcPhase) {
489
500
// This is a host crate.
490
501
// When we're running `cargo-miri` from `x.py` we need to pass the sysroot explicitly
491
502
// 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" ) {
493
504
cmd. arg ( "--sysroot" ) . arg ( sysroot) ;
494
505
}
495
506
@@ -532,7 +543,7 @@ pub enum RunnerPhase {
532
543
}
533
544
534
545
pub 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" )
536
547
. map_or ( 0 , |verbose| verbose. parse ( ) . expect ( "verbosity flag must be an integer" ) ) ;
537
548
538
549
let binary = binary_args. next ( ) . unwrap ( ) ;
@@ -541,6 +552,7 @@ pub fn phase_runner(mut binary_args: impl Iterator<Item = String>, phase: Runner
541
552
"file {:?} not found or `cargo-miri` invoked incorrectly; please only invoke this binary through `cargo miri`" , binary
542
553
) ) ;
543
554
let file = BufReader :: new ( file) ;
555
+ let binary_args = binary_args. collect :: < Vec < _ > > ( ) ;
544
556
545
557
let info = serde_json:: from_reader ( file) . unwrap_or_else ( |_| {
546
558
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
555
567
}
556
568
} ;
557
569
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" {
574
582
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
- ) ;
579
583
}
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) ;
580
596
}
581
- cmd. env ( name, val) ;
582
- }
583
597
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}" ) ) ;
609
634
}
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 ( ) ) ;
625
635
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
+ } ) ;
632
674
}
633
675
634
676
pub fn phase_rustdoc ( mut args : impl Iterator < Item = String > ) {
635
- let verbose = std :: env:: var ( "MIRI_VERBOSE" )
677
+ let verbose = env:: var ( "MIRI_VERBOSE" )
636
678
. map_or ( 0 , |verbose| verbose. parse ( ) . expect ( "verbosity flag must be an integer" ) ) ;
637
679
638
680
// 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>) {
681
723
cmd. arg ( "--cfg" ) . arg ( "miri" ) ;
682
724
683
725
// 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" ) ;
685
727
cmd. arg ( "--test-builder" ) . arg ( & cargo_miri_path) ; // invoked by forwarding most arguments
686
728
cmd. arg ( "--runtool" ) . arg ( & cargo_miri_path) ; // invoked with just a single path argument
687
729
0 commit comments