11use std:: env;
22use std:: ffi:: OsString ;
33use std:: fs:: { self , File } ;
4+ use std:: iter:: TakeWhile ;
45use std:: io:: { self , BufRead , BufReader , BufWriter , Read , Write } ;
56use std:: ops:: Not ;
67use std:: path:: { Path , PathBuf } ;
@@ -36,9 +37,9 @@ enum MiriCommand {
3637 Setup ,
3738}
3839
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 .
4041#[ derive( Serialize , Deserialize ) ]
41- struct CrateRunInfo {
42+ struct CrateRunEnv {
4243 /// The command-line arguments.
4344 args : Vec < String > ,
4445 /// The environment.
@@ -49,6 +50,15 @@ struct CrateRunInfo {
4950 stdin : Vec < u8 > ,
5051}
5152
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+
5262impl CrateRunInfo {
5363 /// Gather all the information we need.
5464 fn collect ( args : env:: Args ) -> Self {
@@ -61,7 +71,7 @@ impl CrateRunInfo {
6171 std:: io:: stdin ( ) . lock ( ) . read_to_end ( & mut stdin) . expect ( "cannot read stdin" ) ;
6272 }
6373
64- CrateRunInfo { args, env, current_dir, stdin }
74+ Self :: RunWith ( CrateRunEnv { args, env, current_dir, stdin } )
6575 }
6676
6777 fn store ( & self , filename : & Path ) {
@@ -97,31 +107,50 @@ fn has_arg_flag(name: &str) -> bool {
97107 args. any ( |val| val == name)
98108}
99109
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,
111122 }
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+ }
121145 }
122146 }
123147}
124148
149+ /// Gets the value of a `--flag`.
150+ fn get_arg_flag_value ( name : & str ) -> Option < String > {
151+ ArgFlagValueIter :: new ( name) . next ( )
152+ }
153+
125154/// Returns the path to the `miri` binary
126155fn find_miri ( ) -> PathBuf {
127156 if let Some ( path) = env:: var_os ( "MIRI" ) {
@@ -460,14 +489,15 @@ fn phase_cargo_miri(mut args: env::Args) {
460489 // This is needed to make the `CARGO_TARGET_*_RUNNER` env var do something,
461490 // and it later helps us detect which crates are proc-macro/build-script
462491 // (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 {
464495 target
465496 } else {
466497 // No target given. Pick default and tell cargo about it.
467- let host = version_info ( ) . host ;
468498 cmd. arg ( "--target" ) ;
469499 cmd. arg ( & host) ;
470- host
500+ & host
471501 } ;
472502
473503 // 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) {
519549 }
520550 cmd. env ( "RUSTC_WRAPPER" , & cargo_miri_path) ;
521551
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) ;
525562
526563 // Set rustdoc to us as well, so we can make it do nothing (see issue #584).
527564 cmd. env ( "RUSTDOC" , & cargo_miri_path) ;
528565
529566 // Run cargo.
530567 if verbose {
531568 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+ }
533573 eprintln ! ( "[cargo-miri miri] RUSTDOC={:?}" , cargo_miri_path) ;
534574 eprintln ! ( "[cargo-miri miri] {:?}" , cmd) ;
535575 cmd. env ( "MIRI_VERBOSE" , "" ) ; // This makes the other phases verbose.
@@ -597,28 +637,38 @@ fn phase_cargo_rustc(args: env::Args) {
597637 _ => { } ,
598638 }
599639
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 | {
607641 let filename = out_filename ( "" , "" ) ;
608642 if verbose {
609643 eprintln ! ( "[cargo-miri rustc] writing run info to `{}`" , filename. display( ) ) ;
610644 }
611-
612645 info. store ( & filename) ;
613646 // For Windows, do the same thing again with `.exe` appended to the filename.
614647 // (Need to do this here as cargo moves that "binary" to a different place before running it.)
615648 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) ;
616661
617662 // Rustdoc expects us to exit with an error code if the test is marked as `compile_fail`,
618663 // just creating the JSON file is not enough: we need to detect syntax errors,
619664 // so we need to run Miri with `MIRI_BE_RUSTC` for a check-only build.
620665 if std:: env:: var_os ( "MIRI_CALLED_FROM_RUSTDOC" ) . is_some ( ) {
621666 let mut cmd = miri ( ) ;
667+ let env = if let CrateRunInfo :: RunWith ( env) = info {
668+ env
669+ } else {
670+ return ;
671+ } ;
622672
623673 // use our own sysroot
624674 if !has_arg_flag ( "--sysroot" ) {
@@ -628,28 +678,36 @@ fn phase_cargo_rustc(args: env::Args) {
628678 }
629679
630680 // 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=" ) ) {
632682 // We need to make sure we're not producing a binary that overwrites the JSON file.
633683 // 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" ) ;
635685 } else {
636686 cmd. arg ( "--emit=dep-info,metadata" ) ;
637687 }
638688
639- cmd. args ( info . args ) ;
689+ cmd. args ( env . args ) ;
640690 cmd. env ( "MIRI_BE_RUSTC" , "1" ) ;
641691
642692 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( ) ) ;
644694 eprintln ! ( "[cargo-miri rustc] {:?}" , cmd) ;
645695 }
646696
647- exec_with_pipe ( cmd, & info . stdin ) ;
697+ exec_with_pipe ( cmd, & env . stdin ) ;
648698 }
649699
650700 return ;
651701 }
652702
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+
653711 let mut cmd = miri ( ) ;
654712 let mut emit_link_hack = false ;
655713 // 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) {
726784 let file = File :: open ( & binary)
727785 . unwrap_or_else ( |_| show_error ( format ! ( "file {:?} not found or `cargo-miri` invoked incorrectly; please only invoke this binary through `cargo miri`" , binary) ) ) ;
728786 let file = BufReader :: new ( file) ;
729- let info: CrateRunInfo = serde_json:: from_reader ( file)
787+
788+ let info = serde_json:: from_reader ( file)
730789 . 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+ } ;
731797
732798 let mut cmd = miri ( ) ;
733799
0 commit comments