1
1
use std:: env;
2
2
use std:: ffi:: OsString ;
3
3
use std:: fs:: { self , File } ;
4
+ use std:: iter:: TakeWhile ;
4
5
use std:: io:: { self , BufRead , BufReader , BufWriter , Read , Write } ;
5
6
use std:: ops:: Not ;
6
7
use std:: path:: { Path , PathBuf } ;
@@ -36,9 +37,9 @@ enum MiriCommand {
36
37
Setup ,
37
38
}
38
39
39
- /// The inforamtion Miri needs to run a crate. Stored as JSON when the crate is "compiled" .
40
+ /// The information to run a crate with the given environment .
40
41
#[ derive( Serialize , Deserialize ) ]
41
- struct CrateRunInfo {
42
+ struct CrateRunEnv {
42
43
/// The command-line arguments.
43
44
args : Vec < String > ,
44
45
/// The environment.
@@ -49,6 +50,15 @@ struct CrateRunInfo {
49
50
stdin : Vec < u8 > ,
50
51
}
51
52
53
+ /// The information Miri needs to run a crate. Stored as JSON when the crate is "compiled".
54
+ #[ derive( Serialize , Deserialize ) ]
55
+ enum CrateRunInfo {
56
+ /// Run it with the given environment.
57
+ RunWith ( CrateRunEnv ) ,
58
+ /// Skip it as Miri does not support interpreting such kind of crates.
59
+ SkipProcMacroTest ,
60
+ }
61
+
52
62
impl CrateRunInfo {
53
63
/// Gather all the information we need.
54
64
fn collect ( args : env:: Args ) -> Self {
@@ -61,7 +71,7 @@ impl CrateRunInfo {
61
71
std:: io:: stdin ( ) . lock ( ) . read_to_end ( & mut stdin) . expect ( "cannot read stdin" ) ;
62
72
}
63
73
64
- CrateRunInfo { args, env, current_dir, stdin }
74
+ Self :: RunWith ( CrateRunEnv { args, env, current_dir, stdin } )
65
75
}
66
76
67
77
fn store ( & self , filename : & Path ) {
@@ -97,31 +107,50 @@ fn has_arg_flag(name: &str) -> bool {
97
107
args. any ( |val| val == name)
98
108
}
99
109
100
- /// Gets the value of a `--flag`.
101
- fn get_arg_flag_value ( name : & str ) -> Option < String > {
102
- // Stop searching at `--`.
103
- let mut args = std:: env:: args ( ) . take_while ( |val| val != "--" ) ;
104
- loop {
105
- let arg = match args. next ( ) {
106
- Some ( arg) => arg,
107
- None => return None ,
108
- } ;
109
- if !arg. starts_with ( name) {
110
- continue ;
110
+ /// Yields all values of command line flag `name`.
111
+ struct ArgFlagValueIter < ' a > {
112
+ args : TakeWhile < env:: Args , fn ( & String ) -> bool > ,
113
+ name : & ' a str ,
114
+ }
115
+
116
+ impl < ' a > ArgFlagValueIter < ' a > {
117
+ fn new ( name : & ' a str ) -> Self {
118
+ Self {
119
+ // Stop searching at `--`.
120
+ args : env:: args ( ) . take_while ( |val| val != "--" ) ,
121
+ name,
111
122
}
112
- // Strip leading `name`.
113
- let suffix = & arg[ name. len ( ) ..] ;
114
- if suffix. is_empty ( ) {
115
- // This argument is exactly `name`; the next one is the value.
116
- return args. next ( ) ;
117
- } else if suffix. starts_with ( '=' ) {
118
- // This argument is `name=value`; get the value.
119
- // Strip leading `=`.
120
- return Some ( suffix[ 1 ..] . to_owned ( ) ) ;
123
+ }
124
+ }
125
+
126
+ impl Iterator for ArgFlagValueIter < ' _ > {
127
+ type Item = String ;
128
+
129
+ fn next ( & mut self ) -> Option < Self :: Item > {
130
+ loop {
131
+ let arg = self . args . next ( ) ?;
132
+ if !arg. starts_with ( self . name ) {
133
+ continue ;
134
+ }
135
+ // Strip leading `name`.
136
+ let suffix = & arg[ self . name . len ( ) ..] ;
137
+ if suffix. is_empty ( ) {
138
+ // This argument is exactly `name`; the next one is the value.
139
+ return self . args . next ( ) ;
140
+ } else if suffix. starts_with ( '=' ) {
141
+ // This argument is `name=value`; get the value.
142
+ // Strip leading `=`.
143
+ return Some ( suffix[ 1 ..] . to_owned ( ) ) ;
144
+ }
121
145
}
122
146
}
123
147
}
124
148
149
+ /// Gets the value of a `--flag`.
150
+ fn get_arg_flag_value ( name : & str ) -> Option < String > {
151
+ ArgFlagValueIter :: new ( name) . next ( )
152
+ }
153
+
125
154
/// Returns the path to the `miri` binary
126
155
fn find_miri ( ) -> PathBuf {
127
156
if let Some ( path) = env:: var_os ( "MIRI" ) {
@@ -460,14 +489,15 @@ fn phase_cargo_miri(mut args: env::Args) {
460
489
// This is needed to make the `CARGO_TARGET_*_RUNNER` env var do something,
461
490
// and it later helps us detect which crates are proc-macro/build-script
462
491
// (host crates) and which crates are needed for the program itself.
463
- let target = if let Some ( target) = get_arg_flag_value ( "--target" ) {
492
+ let host = version_info ( ) . host ;
493
+ let target = get_arg_flag_value ( "--target" ) ;
494
+ let target = if let Some ( ref target) = target {
464
495
target
465
496
} else {
466
497
// No target given. Pick default and tell cargo about it.
467
- let host = version_info ( ) . host ;
468
498
cmd. arg ( "--target" ) ;
469
499
cmd. arg ( & host) ;
470
- host
500
+ & host
471
501
} ;
472
502
473
503
// Forward all further arguments. We do some processing here because we want to
@@ -519,17 +549,27 @@ fn phase_cargo_miri(mut args: env::Args) {
519
549
}
520
550
cmd. env ( "RUSTC_WRAPPER" , & cargo_miri_path) ;
521
551
522
- // Set the runner for the current target to us as well, so we can interpret the binaries.
523
- let runner_env_name = format ! ( "CARGO_TARGET_{}_RUNNER" , target. to_uppercase( ) . replace( '-' , "_" ) ) ;
524
- cmd. env ( & runner_env_name, & cargo_miri_path) ;
552
+ let runner_env_name = |triple : & str | {
553
+ format ! ( "CARGO_TARGET_{}_RUNNER" , triple. to_uppercase( ) . replace( '-' , "_" ) )
554
+ } ;
555
+ let host_runner_env_name = runner_env_name ( & host) ;
556
+ let target_runner_env_name = runner_env_name ( target) ;
557
+ // Set the target runner to us, so we can interpret the binaries.
558
+ cmd. env ( & target_runner_env_name, & cargo_miri_path) ;
559
+ // Unit tests of `proc-macro` crates are run on the host, so we set the host runner to
560
+ // us in order to skip them.
561
+ cmd. env ( & host_runner_env_name, & cargo_miri_path) ;
525
562
526
563
// Set rustdoc to us as well, so we can make it do nothing (see issue #584).
527
564
cmd. env ( "RUSTDOC" , & cargo_miri_path) ;
528
565
529
566
// Run cargo.
530
567
if verbose {
531
568
eprintln ! ( "[cargo-miri miri] RUSTC_WRAPPER={:?}" , cargo_miri_path) ;
532
- eprintln ! ( "[cargo-miri miri] {}={:?}" , runner_env_name, cargo_miri_path) ;
569
+ eprintln ! ( "[cargo-miri miri] {}={:?}" , target_runner_env_name, cargo_miri_path) ;
570
+ if * target != host {
571
+ eprintln ! ( "[cargo-miri miri] {}={:?}" , host_runner_env_name, cargo_miri_path) ;
572
+ }
533
573
eprintln ! ( "[cargo-miri miri] RUSTDOC={:?}" , cargo_miri_path) ;
534
574
eprintln ! ( "[cargo-miri miri] {:?}" , cmd) ;
535
575
cmd. env ( "MIRI_VERBOSE" , "" ) ; // This makes the other phases verbose.
@@ -597,28 +637,38 @@ fn phase_cargo_rustc(args: env::Args) {
597
637
_ => { } ,
598
638
}
599
639
600
- if !print && target_crate && is_runnable_crate ( ) {
601
- // This is the binary or test crate that we want to interpret under Miri.
602
- // But we cannot run it here, as cargo invoked us as a compiler -- our stdin and stdout are not
603
- // like we want them.
604
- // Instead of compiling, we write JSON into the output file with all the relevant command-line flags
605
- // and environment variables; this is used when cargo calls us again in the CARGO_TARGET_RUNNER phase.
606
- let info = CrateRunInfo :: collect ( args) ;
640
+ let store_json = |info : & CrateRunInfo | {
607
641
let filename = out_filename ( "" , "" ) ;
608
642
if verbose {
609
643
eprintln ! ( "[cargo-miri rustc] writing run info to `{}`" , filename. display( ) ) ;
610
644
}
611
-
612
645
info. store ( & filename) ;
613
646
// For Windows, do the same thing again with `.exe` appended to the filename.
614
647
// (Need to do this here as cargo moves that "binary" to a different place before running it.)
615
648
info. store ( & out_filename ( "" , ".exe" ) ) ;
649
+ } ;
650
+
651
+ let runnable_crate = !print && is_runnable_crate ( ) ;
652
+
653
+ if runnable_crate && target_crate {
654
+ // This is the binary or test crate that we want to interpret under Miri.
655
+ // But we cannot run it here, as cargo invoked us as a compiler -- our stdin and stdout are not
656
+ // like we want them.
657
+ // Instead of compiling, we write JSON into the output file with all the relevant command-line flags
658
+ // and environment variables; this is used when cargo calls us again in the CARGO_TARGET_RUNNER phase.
659
+ let info = CrateRunInfo :: collect ( args) ;
660
+ store_json ( & info) ;
616
661
617
662
// Rustdoc expects us to exit with an error code if the test is marked as `compile_fail`,
618
663
// just creating the JSON file is not enough: we need to detect syntax errors,
619
664
// so we need to run Miri with `MIRI_BE_RUSTC` for a check-only build.
620
665
if std:: env:: var_os ( "MIRI_CALLED_FROM_RUSTDOC" ) . is_some ( ) {
621
666
let mut cmd = miri ( ) ;
667
+ let env = if let CrateRunInfo :: RunWith ( env) = info {
668
+ env
669
+ } else {
670
+ return ;
671
+ } ;
622
672
623
673
// use our own sysroot
624
674
if !has_arg_flag ( "--sysroot" ) {
@@ -628,28 +678,36 @@ fn phase_cargo_rustc(args: env::Args) {
628
678
}
629
679
630
680
// ensure --emit argument for a check-only build is present
631
- if let Some ( i) = info . args . iter ( ) . position ( |arg| arg. starts_with ( "--emit=" ) ) {
681
+ if let Some ( i) = env . args . iter ( ) . position ( |arg| arg. starts_with ( "--emit=" ) ) {
632
682
// We need to make sure we're not producing a binary that overwrites the JSON file.
633
683
// rustdoc should only ever pass an --emit=metadata argument for tests marked as `no_run`:
634
- assert_eq ! ( info . args[ i] , "--emit=metadata" ) ;
684
+ assert_eq ! ( env . args[ i] , "--emit=metadata" ) ;
635
685
} else {
636
686
cmd. arg ( "--emit=dep-info,metadata" ) ;
637
687
}
638
688
639
- cmd. args ( info . args ) ;
689
+ cmd. args ( env . args ) ;
640
690
cmd. env ( "MIRI_BE_RUSTC" , "1" ) ;
641
691
642
692
if verbose {
643
- eprintln ! ( "[cargo-miri rustc] captured input:\n {}" , std:: str :: from_utf8( & info . stdin) . unwrap( ) ) ;
693
+ eprintln ! ( "[cargo-miri rustc] captured input:\n {}" , std:: str :: from_utf8( & env . stdin) . unwrap( ) ) ;
644
694
eprintln ! ( "[cargo-miri rustc] {:?}" , cmd) ;
645
695
}
646
696
647
- exec_with_pipe ( cmd, & info . stdin ) ;
697
+ exec_with_pipe ( cmd, & env . stdin ) ;
648
698
}
649
699
650
700
return ;
651
701
}
652
702
703
+ if runnable_crate && ArgFlagValueIter :: new ( "--extern" ) . any ( |krate| krate == "proc_macro" ) {
704
+ // This is a "runnable" `proc-macro` crate (unit tests). We do not support
705
+ // interpreting that under Miri now, so we write a JSON file to (display a
706
+ // helpful message and) skip it in the runner phase.
707
+ store_json ( & CrateRunInfo :: SkipProcMacroTest ) ;
708
+ return ;
709
+ }
710
+
653
711
let mut cmd = miri ( ) ;
654
712
let mut emit_link_hack = false ;
655
713
// Arguments are treated very differently depending on whether this crate is
@@ -726,8 +784,16 @@ fn phase_cargo_runner(binary: &Path, binary_args: env::Args) {
726
784
let file = File :: open ( & binary)
727
785
. unwrap_or_else ( |_| show_error ( format ! ( "file {:?} not found or `cargo-miri` invoked incorrectly; please only invoke this binary through `cargo miri`" , binary) ) ) ;
728
786
let file = BufReader :: new ( file) ;
729
- let info: CrateRunInfo = serde_json:: from_reader ( file)
787
+
788
+ let info = serde_json:: from_reader ( file)
730
789
. unwrap_or_else ( |_| show_error ( format ! ( "file {:?} contains outdated or invalid JSON; try `cargo clean`" , binary) ) ) ;
790
+ let info = match info {
791
+ CrateRunInfo :: RunWith ( info) => info,
792
+ CrateRunInfo :: SkipProcMacroTest => {
793
+ eprintln ! ( "Running unit tests of `proc-macro` crates is not currently supported by Miri." ) ;
794
+ return ;
795
+ }
796
+ } ;
731
797
732
798
let mut cmd = miri ( ) ;
733
799
0 commit comments