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
@@ -34,6 +34,8 @@ Examples:
34
34
35
35
" ;
36
36
37
+ const DEFAULT_MANY_SEEDS : & str = "0..64" ;
38
+
37
39
fn show_help ( ) {
38
40
println ! ( "{CARGO_MIRI_HELP}" ) ;
39
41
}
@@ -119,7 +121,7 @@ pub fn phase_cargo_miri(mut args: impl Iterator<Item = String>) {
119
121
// <https://github.com/rust-lang/miri/pull/1540#issuecomment-693553191> describes an alternative
120
122
// approach that uses `cargo check`, making that part easier but target and binary handling
121
123
// harder.
122
- let cargo_miri_path = std :: env:: current_exe ( )
124
+ let cargo_miri_path = env:: current_exe ( )
123
125
. expect ( "current executable path invalid" )
124
126
. into_os_string ( )
125
127
. into_string ( )
@@ -163,14 +165,22 @@ pub fn phase_cargo_miri(mut args: impl Iterator<Item = String>) {
163
165
let target_dir = get_target_dir ( & metadata) ;
164
166
cmd. arg ( "--target-dir" ) . arg ( target_dir) ;
165
167
168
+ // Store many-seeds argument.
169
+ let mut many_seeds = None ;
166
170
// *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).
171
+ // `--target-dir` (which would otherwise be set twice) and `--many-seeds` (which is our flag, not cargo's) .
168
172
for arg in
169
173
ArgSplitFlagValue :: from_string_iter ( & mut args, "--target-dir" ) . filter_map ( Result :: err)
170
174
{
171
- cmd. arg ( arg) ;
175
+ if arg == "--many-seeds" {
176
+ many_seeds = Some ( DEFAULT_MANY_SEEDS . to_owned ( ) ) ;
177
+ } else if let Some ( val) = arg. strip_prefix ( "--many-seeds=" ) {
178
+ many_seeds = Some ( val. to_owned ( ) ) ;
179
+ } else {
180
+ cmd. arg ( arg) ;
181
+ }
172
182
}
173
- // Forward all further arguments (not consumed by `ArgSplitFlagValue`) to cargo.
183
+ // Forward all further arguments after `--` (not consumed by `ArgSplitFlagValue`) to cargo.
174
184
cmd. args ( args) ;
175
185
176
186
// Set `RUSTC_WRAPPER` to ourselves. Cargo will prepend that binary to its usual invocation,
@@ -222,6 +232,9 @@ pub fn phase_cargo_miri(mut args: impl Iterator<Item = String>) {
222
232
// Forward some crucial information to our own re-invocations.
223
233
cmd. env ( "MIRI_SYSROOT" , miri_sysroot) ;
224
234
cmd. env ( "MIRI_LOCAL_CRATES" , local_crates ( & metadata) ) ;
235
+ if let Some ( many_seeds) = many_seeds {
236
+ cmd. env ( "MIRI_MANY_SEEDS" , many_seeds) ;
237
+ }
225
238
if verbose > 0 {
226
239
cmd. env ( "MIRI_VERBOSE" , verbose. to_string ( ) ) ; // This makes the other phases verbose.
227
240
}
@@ -309,7 +322,7 @@ pub fn phase_rustc(mut args: impl Iterator<Item = String>, phase: RustcPhase) {
309
322
}
310
323
}
311
324
312
- let verbose = std :: env:: var ( "MIRI_VERBOSE" )
325
+ let verbose = env:: var ( "MIRI_VERBOSE" )
313
326
. map_or ( 0 , |verbose| verbose. parse ( ) . expect ( "verbosity flag must be an integer" ) ) ;
314
327
let target_crate = is_target_crate ( ) ;
315
328
@@ -489,7 +502,7 @@ pub fn phase_rustc(mut args: impl Iterator<Item = String>, phase: RustcPhase) {
489
502
// This is a host crate.
490
503
// When we're running `cargo-miri` from `x.py` we need to pass the sysroot explicitly
491
504
// due to bootstrap complications.
492
- if let Some ( sysroot) = std :: env:: var_os ( "MIRI_HOST_SYSROOT" ) {
505
+ if let Some ( sysroot) = env:: var_os ( "MIRI_HOST_SYSROOT" ) {
493
506
cmd. arg ( "--sysroot" ) . arg ( sysroot) ;
494
507
}
495
508
@@ -532,7 +545,7 @@ pub enum RunnerPhase {
532
545
}
533
546
534
547
pub fn phase_runner ( mut binary_args : impl Iterator < Item = String > , phase : RunnerPhase ) {
535
- let verbose = std :: env:: var ( "MIRI_VERBOSE" )
548
+ let verbose = env:: var ( "MIRI_VERBOSE" )
536
549
. map_or ( 0 , |verbose| verbose. parse ( ) . expect ( "verbosity flag must be an integer" ) ) ;
537
550
538
551
let binary = binary_args. next ( ) . unwrap ( ) ;
@@ -541,6 +554,7 @@ pub fn phase_runner(mut binary_args: impl Iterator<Item = String>, phase: Runner
541
554
"file {:?} not found or `cargo-miri` invoked incorrectly; please only invoke this binary through `cargo miri`" , binary
542
555
) ) ;
543
556
let file = BufReader :: new ( file) ;
557
+ let binary_args = binary_args. collect :: < Vec < _ > > ( ) ;
544
558
545
559
let info = serde_json:: from_reader ( file) . unwrap_or_else ( |_| {
546
560
show_error ! ( "file {:?} contains outdated or invalid JSON; try `cargo clean`" , binary)
@@ -555,84 +569,114 @@ pub fn phase_runner(mut binary_args: impl Iterator<Item = String>, phase: Runner
555
569
}
556
570
} ;
557
571
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.)
572
+ let many_seeds = env:: var ( "MIRI_MANY_SEEDS" ) ;
573
+ run_many_seeds ( many_seeds. ok ( ) , |seed| {
574
+ let mut cmd = miri ( ) ;
575
+
576
+ // Set missing env vars. We prefer build-time env vars over run-time ones; see
577
+ // <https://github.com/rust-lang/miri/issues/1661> for the kind of issue that fixes.
578
+ for ( name, val) in & info. env {
579
+ // `CARGO_MAKEFLAGS` contains information about how to reach the jobserver, but by the time
580
+ // the program is being run, that jobserver no longer exists (cargo only runs the jobserver
581
+ // for the build portion of `cargo run`/`cargo test`). Hence we shouldn't forward this.
582
+ // Also see <https://github.com/rust-lang/rust/pull/113730>.
583
+ if name == "CARGO_MAKEFLAGS" {
574
584
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
585
}
586
+ if let Some ( old_val) = env:: var_os ( name) {
587
+ if * old_val == * val {
588
+ // This one did not actually change, no need to re-set it.
589
+ // (This keeps the `debug_cmd` below more manageable.)
590
+ continue ;
591
+ } else if verbose > 0 {
592
+ eprintln ! (
593
+ "[cargo-miri runner] Overwriting run-time env var {name:?}={old_val:?} with build-time value {val:?}"
594
+ ) ;
595
+ }
596
+ }
597
+ cmd. env ( name, val) ;
580
598
}
581
- cmd. env ( name, val) ;
582
- }
583
599
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) ;
600
+ if phase != RunnerPhase :: Rustdoc {
601
+ // Set the sysroot. Not necessary in rustdoc, where we already set the sysroot in
602
+ // `phase_rustdoc`. rustdoc will forward that flag when invoking rustc (i.e., us), so the
603
+ // flag is present in `info.args`.
604
+ cmd. arg ( "--sysroot" ) . arg ( env:: var_os ( "MIRI_SYSROOT" ) . unwrap ( ) ) ;
605
+ }
606
+ // Forward rustc arguments.
607
+ // We need to patch "--extern" filenames because we forced a check-only
608
+ // build without cargo knowing about that: replace `.rlib` suffix by
609
+ // `.rmeta`.
610
+ // We also need to remove `--error-format` as cargo specifies that to be JSON,
611
+ // but when we run here, cargo does not interpret the JSON any more. `--json`
612
+ // then also needs to be dropped.
613
+ let mut args = info. args . iter ( ) ;
614
+ while let Some ( arg) = args. next ( ) {
615
+ if arg == "--extern" {
616
+ forward_patched_extern_arg ( & mut ( & mut args) . cloned ( ) , & mut cmd) ;
617
+ } else if let Some ( suffix) = arg. strip_prefix ( "--error-format" ) {
618
+ assert ! ( suffix. starts_with( '=' ) ) ;
619
+ // Drop this argument.
620
+ } else if let Some ( suffix) = arg. strip_prefix ( "--json" ) {
621
+ assert ! ( suffix. starts_with( '=' ) ) ;
622
+ // Drop this argument.
623
+ } else {
624
+ cmd. arg ( arg) ;
625
+ }
626
+ }
627
+ // Respect `MIRIFLAGS`.
628
+ if let Ok ( a) = env:: var ( "MIRIFLAGS" ) {
629
+ let args = flagsplit ( & a) ;
630
+ cmd. args ( args) ;
631
+ }
632
+ // Set the current seed.
633
+ if let Some ( seed) = seed {
634
+ eprintln ! ( "Trying seed: {seed}" ) ;
635
+ cmd. arg ( format ! ( "-Zmiri-seed={seed}" ) ) ;
609
636
}
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
637
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
- }
638
+ // Then pass binary arguments.
639
+ cmd. arg ( "--" ) ;
640
+ cmd. args ( & binary_args) ;
641
+
642
+ // Make sure we use the build-time working directory for interpreting Miri/rustc arguments.
643
+ // But then we need to switch to the run-time one, which we instruct Miri to do by setting `MIRI_CWD`.
644
+ cmd. current_dir ( & info. current_dir ) ;
645
+ cmd. env ( "MIRI_CWD" , env:: current_dir ( ) . unwrap ( ) ) ;
646
+
647
+ // Run it.
648
+ debug_cmd ( "[cargo-miri runner]" , verbose, & cmd) ;
649
+
650
+ match phase {
651
+ RunnerPhase :: Rustdoc => {
652
+ cmd. stdin ( std:: process:: Stdio :: piped ( ) ) ;
653
+ let mut child = cmd. spawn ( ) . expect ( "failed to spawn process" ) ;
654
+ let child_stdin = child. stdin . take ( ) . unwrap ( ) ;
655
+ // Write stdin in a background thread, as it may block.
656
+ let exit_status = thread:: scope ( |s| {
657
+ s. spawn ( || {
658
+ let mut child_stdin = child_stdin;
659
+ // Ignore failure, it is most likely due to the process having terminated.
660
+ let _ = child_stdin. write_all ( & info. stdin ) ;
661
+ } ) ;
662
+ child. wait ( ) . expect ( "failed to run command" )
663
+ } ) ;
664
+ if !exit_status. success ( ) {
665
+ std:: process:: exit ( exit_status. code ( ) . unwrap_or ( -1 ) ) ;
666
+ }
667
+ }
668
+ RunnerPhase :: Cargo => {
669
+ let exit_status = cmd. status ( ) . expect ( "failed to run command" ) ;
670
+ if !exit_status. success ( ) {
671
+ std:: process:: exit ( exit_status. code ( ) . unwrap_or ( -1 ) ) ;
672
+ }
673
+ }
674
+ }
675
+ } ) ;
632
676
}
633
677
634
678
pub fn phase_rustdoc ( mut args : impl Iterator < Item = String > ) {
635
- let verbose = std :: env:: var ( "MIRI_VERBOSE" )
679
+ let verbose = env:: var ( "MIRI_VERBOSE" )
636
680
. map_or ( 0 , |verbose| verbose. parse ( ) . expect ( "verbosity flag must be an integer" ) ) ;
637
681
638
682
// phase_cargo_miri sets the RUSTDOC env var to ourselves, and puts a backup
@@ -681,7 +725,7 @@ pub fn phase_rustdoc(mut args: impl Iterator<Item = String>) {
681
725
cmd. arg ( "--cfg" ) . arg ( "miri" ) ;
682
726
683
727
// Make rustdoc call us back.
684
- let cargo_miri_path = std :: env:: current_exe ( ) . expect ( "current executable path invalid" ) ;
728
+ let cargo_miri_path = env:: current_exe ( ) . expect ( "current executable path invalid" ) ;
685
729
cmd. arg ( "--test-builder" ) . arg ( & cargo_miri_path) ; // invoked by forwarding most arguments
686
730
cmd. arg ( "--runtool" ) . arg ( & cargo_miri_path) ; // invoked with just a single path argument
687
731
0 commit comments