11use std:: env;
22use std:: ffi:: OsString ;
33use std:: fs:: { self , File } ;
4- use std:: io:: { self , BufRead , BufReader , BufWriter , Write } ;
4+ use std:: io:: { self , BufRead , BufReader , BufWriter , Read , Write } ;
55use std:: ops:: Not ;
66use std:: path:: { Path , PathBuf } ;
77use std:: process:: Command ;
@@ -45,6 +45,8 @@ struct CrateRunInfo {
4545 env : Vec < ( OsString , OsString ) > ,
4646 /// The current working directory.
4747 current_dir : OsString ,
48+ /// The contents passed via standard input.
49+ stdin : Vec < u8 > ,
4850}
4951
5052impl CrateRunInfo {
@@ -53,7 +55,13 @@ impl CrateRunInfo {
5355 let args = args. collect ( ) ;
5456 let env = env:: vars_os ( ) . collect ( ) ;
5557 let current_dir = env:: current_dir ( ) . unwrap ( ) . into_os_string ( ) ;
56- CrateRunInfo { args, env, current_dir }
58+
59+ let mut stdin = Vec :: new ( ) ;
60+ if env:: var_os ( "MIRI_CALLED_FROM_RUSTDOC" ) . is_some ( ) {
61+ std:: io:: stdin ( ) . lock ( ) . read_to_end ( & mut stdin) . expect ( "cannot read stdin" ) ;
62+ }
63+
64+ CrateRunInfo { args, env, current_dir, stdin }
5765 }
5866
5967 fn store ( & self , filename : & Path ) {
@@ -539,17 +547,22 @@ fn phase_cargo_rustc(args: env::Args) {
539547 }
540548
541549 fn out_filename ( prefix : & str , suffix : & str ) -> PathBuf {
542- let mut path = PathBuf :: from ( get_arg_flag_value ( "--out-dir" ) . unwrap ( ) ) ;
543- path. push ( format ! (
544- "{}{}{}{}" ,
545- prefix,
546- get_arg_flag_value( "--crate-name" ) . unwrap( ) ,
547- // This is technically a `-C` flag but the prefix seems unique enough...
548- // (and cargo passes this before the filename so it should be unique)
549- get_arg_flag_value( "extra-filename" ) . unwrap_or( String :: new( ) ) ,
550- suffix,
551- ) ) ;
552- path
550+ if let Some ( out_dir) = get_arg_flag_value ( "--out-dir" ) {
551+ let mut path = PathBuf :: from ( out_dir) ;
552+ path. push ( format ! (
553+ "{}{}{}{}" ,
554+ prefix,
555+ get_arg_flag_value( "--crate-name" ) . unwrap( ) ,
556+ // This is technically a `-C` flag but the prefix seems unique enough...
557+ // (and cargo passes this before the filename so it should be unique)
558+ get_arg_flag_value( "extra-filename" ) . unwrap_or( String :: new( ) ) ,
559+ suffix,
560+ ) ) ;
561+ path
562+ } else {
563+ let out_file = get_arg_flag_value ( "-o" ) . unwrap ( ) ;
564+ PathBuf :: from ( out_file)
565+ }
553566 }
554567
555568 let verbose = std:: env:: var_os ( "MIRI_VERBOSE" ) . is_some ( ) ;
@@ -734,6 +747,44 @@ fn phase_cargo_runner(binary: &Path, binary_args: env::Args) {
734747 if verbose {
735748 eprintln ! ( "[cargo-miri runner] {:?}" , cmd) ;
736749 }
750+
751+ cmd. stdin ( std:: process:: Stdio :: piped ( ) ) ;
752+ let mut child = cmd. spawn ( ) . expect ( "failed to spawn miri process" ) ;
753+ {
754+ let stdin = child. stdin . as_mut ( ) . expect ( "failed to open stdin" ) ;
755+ stdin. write_all ( & info. stdin ) . expect ( "failed to write out test source" ) ;
756+ }
757+ let exit_status = child. wait ( ) . expect ( "failed to run command" ) ;
758+ if exit_status. success ( ) . not ( ) {
759+ std:: process:: exit ( exit_status. code ( ) . unwrap_or ( -1 ) )
760+ }
761+ }
762+
763+ fn phase_cargo_rustdoc ( fst_arg : & str , args : env:: Args ) {
764+ let verbose = std:: env:: var_os ( "MIRI_VERBOSE" ) . is_some ( ) ;
765+
766+ // phase_cargo_miri sets the RUSTDOC env var to ourselves, so we can't use that here;
767+ // just default to a straight-forward invocation for now:
768+ let mut cmd = Command :: new ( OsString :: from ( "rustdoc" ) ) ;
769+
770+ // just pass everything through until we find a reason not to do that:
771+ cmd. arg ( fst_arg) ;
772+ cmd. args ( args) ;
773+
774+ cmd. arg ( "-Z" ) . arg ( "unstable-options" ) ;
775+
776+ let cargo_miri_path = std:: env:: current_exe ( ) . expect ( "current executable path invalid" ) ;
777+ cmd. arg ( "--test-builder" ) . arg ( & cargo_miri_path) ;
778+ cmd. arg ( "--runtool" ) . arg ( & cargo_miri_path) ;
779+
780+ // rustdoc passes generated code to rustc via stdin, rather than a temporary file,
781+ // so we need to let the coming invocations know to expect that
782+ cmd. env ( "MIRI_CALLED_FROM_RUSTDOC" , "1" ) ;
783+
784+ if verbose {
785+ eprintln ! ( "[cargo-miri rustdoc] {:?}" , cmd) ;
786+ }
787+
737788 exec ( cmd)
738789}
739790
@@ -750,6 +801,30 @@ fn main() {
750801 return ;
751802 }
752803
804+ // The way rustdoc invokes rustc is indistuingishable from the way cargo invokes rustdoc
805+ // by the arguments alone, and we can't take from the args iterator in this case.
806+ // phase_cargo_rustdoc sets this environment variable to let us disambiguate here
807+ let invoked_as_rustc_from_rustdoc = env:: var_os ( "MIRI_CALLED_FROM_RUSTDOC" ) . is_some ( ) ;
808+ if invoked_as_rustc_from_rustdoc {
809+ // ...however, we then also see this variable when rustdoc invokes us as the testrunner!
810+ // The runner is invoked as `$runtool ($runtool-arg)* output_file;
811+ // since we don't specify any runtool-args, and rustdoc supplies multiple arguments to
812+ // the test-builder unconditionally, we can just check the number of remaining arguments:
813+ if args. len ( ) == 1 {
814+ let arg = args. next ( ) . unwrap ( ) ;
815+ let binary = Path :: new ( & arg) ;
816+ if binary. exists ( ) {
817+ phase_cargo_runner ( binary, args) ;
818+ } else {
819+ show_error ( format ! ( "`cargo-miri` called with non-existing path argument `{}`; please invoke this binary through `cargo miri`" , arg) ) ;
820+ }
821+ } else {
822+ phase_cargo_rustc ( args) ;
823+ }
824+
825+ return ;
826+ }
827+
753828 // Dispatch to `cargo-miri` phase. There are three phases:
754829 // - When we are called via `cargo miri`, we run as the frontend and invoke the underlying
755830 // cargo. We set RUSTC_WRAPPER and CARGO_TARGET_RUNNER to ourselves.
@@ -762,16 +837,15 @@ fn main() {
762837 Some ( "miri" ) => phase_cargo_miri ( args) ,
763838 Some ( "rustc" ) => phase_cargo_rustc ( args) ,
764839 Some ( arg) => {
765- // We have to distinguish the "runner" and "rustfmt " cases.
840+ // We have to distinguish the "runner" and "rustdoc " cases.
766841 // As runner, the first argument is the binary (a file that should exist, with an absolute path);
767- // as rustfmt , the first argument is a flag (`--something`).
842+ // as rustdoc , the first argument is a flag (`--something`).
768843 let binary = Path :: new ( arg) ;
769844 if binary. exists ( ) {
770845 assert ! ( !arg. starts_with( "--" ) ) ; // not a flag
771846 phase_cargo_runner ( binary, args) ;
772847 } else if arg. starts_with ( "--" ) {
773- // We are rustdoc.
774- eprintln ! ( "Running doctests is not currently supported by Miri." )
848+ phase_cargo_rustdoc ( arg, args) ;
775849 } else {
776850 show_error ( format ! ( "`cargo-miri` called with unexpected first argument `{}`; please only invoke this binary through `cargo miri`" , arg) ) ;
777851 }
0 commit comments