1
1
use std:: env;
2
2
use std:: ffi:: OsString ;
3
3
use std:: fs:: { self , File } ;
4
- use std:: io:: { self , BufRead , BufReader , BufWriter , Write } ;
5
4
use std:: iter:: TakeWhile ;
5
+ use std:: io:: { self , BufRead , BufReader , BufWriter , Read , Write } ;
6
6
use std:: ops:: Not ;
7
7
use std:: path:: { Path , PathBuf } ;
8
8
use std:: process:: Command ;
@@ -46,6 +46,24 @@ struct CrateRunEnv {
46
46
env : Vec < ( OsString , OsString ) > ,
47
47
/// The current working directory.
48
48
current_dir : OsString ,
49
+ /// The contents passed via standard input.
50
+ stdin : Vec < u8 > ,
51
+ }
52
+
53
+ impl CrateRunEnv {
54
+ /// Gather all the information we need.
55
+ fn collect ( args : env:: Args , capture_stdin : bool ) -> Self {
56
+ let args = args. collect ( ) ;
57
+ let env = env:: vars_os ( ) . collect ( ) ;
58
+ let current_dir = env:: current_dir ( ) . unwrap ( ) . into_os_string ( ) ;
59
+
60
+ let mut stdin = Vec :: new ( ) ;
61
+ if capture_stdin {
62
+ std:: io:: stdin ( ) . lock ( ) . read_to_end ( & mut stdin) . expect ( "cannot read stdin" ) ;
63
+ }
64
+
65
+ CrateRunEnv { args, env, current_dir, stdin }
66
+ }
49
67
}
50
68
51
69
/// The information Miri needs to run a crate. Stored as JSON when the crate is "compiled".
@@ -58,14 +76,6 @@ enum CrateRunInfo {
58
76
}
59
77
60
78
impl CrateRunInfo {
61
- /// Gather all the information we need.
62
- fn collect ( args : env:: Args ) -> Self {
63
- let args = args. collect ( ) ;
64
- let env = env:: vars_os ( ) . collect ( ) ;
65
- let current_dir = env:: current_dir ( ) . unwrap ( ) . into_os_string ( ) ;
66
- Self :: RunWith ( CrateRunEnv { args, env, current_dir } )
67
- }
68
-
69
79
fn store ( & self , filename : & Path ) {
70
80
let file = File :: create ( filename)
71
81
. unwrap_or_else ( |_| show_error ( format ! ( "cannot create `{}`" , filename. display( ) ) ) ) ;
@@ -155,6 +165,13 @@ fn forward_patched_extern_arg(args: &mut impl Iterator<Item = String>, cmd: &mut
155
165
}
156
166
}
157
167
168
+ fn forward_miri_sysroot ( cmd : & mut Command ) {
169
+ let sysroot =
170
+ env:: var_os ( "MIRI_SYSROOT" ) . expect ( "the wrapper should have set MIRI_SYSROOT" ) ;
171
+ cmd. arg ( "--sysroot" ) ;
172
+ cmd. arg ( sysroot) ;
173
+ }
174
+
158
175
/// Returns the path to the `miri` binary
159
176
fn find_miri ( ) -> PathBuf {
160
177
if let Some ( path) = env:: var_os ( "MIRI" ) {
@@ -190,6 +207,22 @@ fn exec(mut cmd: Command) {
190
207
}
191
208
}
192
209
210
+ /// Execute the command and pipe `input` into its stdin.
211
+ /// If it fails, fail this process with the same exit code.
212
+ /// Otherwise, continue.
213
+ fn exec_with_pipe ( mut cmd : Command , input : & [ u8 ] ) {
214
+ cmd. stdin ( std:: process:: Stdio :: piped ( ) ) ;
215
+ let mut child = cmd. spawn ( ) . expect ( "failed to spawn process" ) ;
216
+ {
217
+ let stdin = child. stdin . as_mut ( ) . expect ( "failed to open stdin" ) ;
218
+ stdin. write_all ( input) . expect ( "failed to write out test source" ) ;
219
+ }
220
+ let exit_status = child. wait ( ) . expect ( "failed to run command" ) ;
221
+ if exit_status. success ( ) . not ( ) {
222
+ std:: process:: exit ( exit_status. code ( ) . unwrap_or ( -1 ) )
223
+ }
224
+ }
225
+
193
226
fn xargo_version ( ) -> Option < ( u32 , u32 , u32 ) > {
194
227
let out = xargo_check ( ) . arg ( "--version" ) . output ( ) . ok ( ) ?;
195
228
if !out. status . success ( ) {
@@ -548,7 +581,7 @@ fn phase_cargo_miri(mut args: env::Args) {
548
581
// us in order to skip them.
549
582
cmd. env ( & host_runner_env_name, & cargo_miri_path) ;
550
583
551
- // Set rustdoc to us as well, so we can make it do nothing (see issue #584) .
584
+ // Set rustdoc to us as well, so we can run doctests .
552
585
cmd. env ( "RUSTDOC" , & cargo_miri_path) ;
553
586
554
587
// Run cargo.
@@ -591,17 +624,22 @@ fn phase_cargo_rustc(mut args: env::Args) {
591
624
}
592
625
593
626
fn out_filename ( prefix : & str , suffix : & str ) -> PathBuf {
594
- let mut path = PathBuf :: from ( get_arg_flag_value ( "--out-dir" ) . unwrap ( ) ) ;
595
- path. push ( format ! (
596
- "{}{}{}{}" ,
597
- prefix,
598
- get_arg_flag_value( "--crate-name" ) . unwrap( ) ,
599
- // This is technically a `-C` flag but the prefix seems unique enough...
600
- // (and cargo passes this before the filename so it should be unique)
601
- get_arg_flag_value( "extra-filename" ) . unwrap_or( String :: new( ) ) ,
602
- suffix,
603
- ) ) ;
604
- path
627
+ if let Some ( out_dir) = get_arg_flag_value ( "--out-dir" ) {
628
+ let mut path = PathBuf :: from ( out_dir) ;
629
+ path. push ( format ! (
630
+ "{}{}{}{}" ,
631
+ prefix,
632
+ get_arg_flag_value( "--crate-name" ) . unwrap( ) ,
633
+ // This is technically a `-C` flag but the prefix seems unique enough...
634
+ // (and cargo passes this before the filename so it should be unique)
635
+ get_arg_flag_value( "extra-filename" ) . unwrap_or( String :: new( ) ) ,
636
+ suffix,
637
+ ) ) ;
638
+ path
639
+ } else {
640
+ let out_file = get_arg_flag_value ( "-o" ) . unwrap ( ) ;
641
+ PathBuf :: from ( out_file)
642
+ }
605
643
}
606
644
607
645
let verbose = std:: env:: var_os ( "MIRI_VERBOSE" ) . is_some ( ) ;
@@ -631,12 +669,43 @@ fn phase_cargo_rustc(mut args: env::Args) {
631
669
let runnable_crate = !print && is_runnable_crate ( ) ;
632
670
633
671
if runnable_crate && target_crate {
672
+ let inside_rustdoc = env:: var_os ( "MIRI_CALLED_FROM_RUSTDOC" ) . is_some ( ) ;
634
673
// This is the binary or test crate that we want to interpret under Miri.
635
674
// But we cannot run it here, as cargo invoked us as a compiler -- our stdin and stdout are not
636
675
// like we want them.
637
676
// Instead of compiling, we write JSON into the output file with all the relevant command-line flags
638
677
// and environment variables; this is used when cargo calls us again in the CARGO_TARGET_RUNNER phase.
639
- store_json ( CrateRunInfo :: collect ( args) ) ;
678
+ let env = CrateRunEnv :: collect ( args, inside_rustdoc) ;
679
+
680
+ // Rustdoc expects us to exit with an error code if the test is marked as `compile_fail`,
681
+ // just creating the JSON file is not enough: we need to detect syntax errors,
682
+ // so we need to run Miri with `MIRI_BE_RUSTC` for a check-only build.
683
+ if inside_rustdoc {
684
+ let mut cmd = miri ( ) ;
685
+
686
+ // Ensure --emit argument for a check-only build is present.
687
+ // We cannot use the usual helpers since we need to check specifically in `env.args`.
688
+ if let Some ( i) = env. args . iter ( ) . position ( |arg| arg. starts_with ( "--emit=" ) ) {
689
+ // For `no_run` tests, rustdoc passes a `--emit` flag; make sure it has the right shape.
690
+ assert_eq ! ( env. args[ i] , "--emit=metadata" ) ;
691
+ } else {
692
+ // For all other kinds of tests, we can just add our flag.
693
+ cmd. arg ( "--emit=metadata" ) ;
694
+ }
695
+
696
+ cmd. args ( & env. args ) ;
697
+ cmd. env ( "MIRI_BE_RUSTC" , "1" ) ;
698
+
699
+ if verbose {
700
+ eprintln ! ( "[cargo-miri rustc] captured input:\n {}" , std:: str :: from_utf8( & env. stdin) . unwrap( ) ) ;
701
+ eprintln ! ( "[cargo-miri rustc] {:?}" , cmd) ;
702
+ }
703
+
704
+ exec_with_pipe ( cmd, & env. stdin ) ;
705
+ }
706
+
707
+ store_json ( CrateRunInfo :: RunWith ( env) ) ;
708
+
640
709
return ;
641
710
}
642
711
@@ -681,10 +750,7 @@ fn phase_cargo_rustc(mut args: env::Args) {
681
750
}
682
751
683
752
// Use our custom sysroot.
684
- let sysroot =
685
- env:: var_os ( "MIRI_SYSROOT" ) . expect ( "the wrapper should have set MIRI_SYSROOT" ) ;
686
- cmd. arg ( "--sysroot" ) ;
687
- cmd. arg ( sysroot) ;
753
+ forward_miri_sysroot ( & mut cmd) ;
688
754
} else {
689
755
// For host crates or when we are printing, just forward everything.
690
756
cmd. args ( args) ;
@@ -772,11 +838,10 @@ fn phase_cargo_runner(binary: &Path, binary_args: env::Args) {
772
838
cmd. arg ( arg) ;
773
839
}
774
840
}
775
- // Set sysroot.
776
- let sysroot =
777
- env:: var_os ( "MIRI_SYSROOT" ) . expect ( "the wrapper should have set MIRI_SYSROOT" ) ;
778
- cmd. arg ( "--sysroot" ) ;
779
- cmd. arg ( sysroot) ;
841
+ if env:: var_os ( "MIRI_CALLED_FROM_RUSTDOC" ) . is_none ( ) {
842
+ // Set sysroot (if we are inside rustdoc, we already did that in `phase_cargo_rustdoc`).
843
+ forward_miri_sysroot ( & mut cmd) ;
844
+ }
780
845
// Respect `MIRIFLAGS`.
781
846
if let Ok ( a) = env:: var ( "MIRIFLAGS" ) {
782
847
// This code is taken from `RUSTFLAGS` handling in cargo.
@@ -801,6 +866,77 @@ fn phase_cargo_runner(binary: &Path, binary_args: env::Args) {
801
866
if verbose {
802
867
eprintln ! ( "[cargo-miri runner] {:?}" , cmd) ;
803
868
}
869
+
870
+ if std:: env:: var_os ( "MIRI_CALLED_FROM_RUSTDOC" ) . is_some ( ) {
871
+ exec_with_pipe ( cmd, & info. stdin )
872
+ } else {
873
+ exec ( cmd)
874
+ }
875
+ }
876
+
877
+ fn phase_cargo_rustdoc ( fst_arg : & str , mut args : env:: Args ) {
878
+ let verbose = std:: env:: var_os ( "MIRI_VERBOSE" ) . is_some ( ) ;
879
+
880
+ // phase_cargo_miri sets the RUSTDOC env var to ourselves, so we can't use that here;
881
+ // just default to a straight-forward invocation for now:
882
+ let mut cmd = Command :: new ( "rustdoc" ) ;
883
+
884
+ // Because of the way the main function is structured, we have to take the first argument spearately
885
+ // from the rest; to simplify the following argument patching loop, we'll just skip that one.
886
+ // This is fine for now, because cargo will never pass --extern arguments in the first position,
887
+ // but we should defensively assert that this will work.
888
+ let extern_flag = "--extern" ;
889
+ assert ! ( fst_arg != extern_flag) ;
890
+ cmd. arg ( fst_arg) ;
891
+
892
+ let runtool_flag = "--runtool" ;
893
+ // `crossmode` records if *any* argument matches `runtool_flag`; here we check the first one.
894
+ let mut crossmode = fst_arg == runtool_flag;
895
+ while let Some ( arg) = args. next ( ) {
896
+ if arg == extern_flag {
897
+ // Patch --extern arguments to use *.rmeta files, since phase_cargo_rustc only creates stub *.rlib files.
898
+ forward_patched_extern_arg ( & mut args, & mut cmd) ;
899
+ } else if arg == runtool_flag {
900
+ // An existing --runtool flag indicates cargo is running in cross-target mode, which we don't support.
901
+ // Note that this is only passed when cargo is run with the unstable -Zdoctest-xcompile flag;
902
+ // otherwise, we won't be called as rustdoc at all.
903
+ crossmode = true ;
904
+ break ;
905
+ } else {
906
+ cmd. arg ( arg) ;
907
+ }
908
+ }
909
+
910
+ if crossmode {
911
+ show_error ( format ! ( "cross-interpreting doc-tests is not currently supported by Miri." ) ) ;
912
+ }
913
+
914
+ // For each doc-test, rustdoc starts two child processes: first the test is compiled,
915
+ // then the produced executable is invoked. We want to reroute both of these to cargo-miri,
916
+ // such that the first time we'll enter phase_cargo_rustc, and phase_cargo_runner second.
917
+ //
918
+ // rustdoc invokes the test-builder by forwarding most of its own arguments, which makes
919
+ // it difficult to determine when phase_cargo_rustc should run instead of phase_cargo_rustdoc.
920
+ // Furthermore, the test code is passed via stdin, rather than a temporary file, so we need
921
+ // to let phase_cargo_rustc know to expect that. We'll use this environment variable as a flag:
922
+ cmd. env ( "MIRI_CALLED_FROM_RUSTDOC" , "1" ) ;
923
+
924
+ // The `--test-builder` and `--runtool` arguments are unstable rustdoc features,
925
+ // which are disabled by default. We first need to enable them explicitly:
926
+ cmd. arg ( "-Z" ) . arg ( "unstable-options" ) ;
927
+
928
+ // rustdoc needs to know the right sysroot.
929
+ forward_miri_sysroot ( & mut cmd) ;
930
+
931
+ // Make rustdoc call us back.
932
+ let cargo_miri_path = std:: env:: current_exe ( ) . expect ( "current executable path invalid" ) ;
933
+ cmd. arg ( "--test-builder" ) . arg ( & cargo_miri_path) ; // invoked by forwarding most arguments
934
+ cmd. arg ( "--runtool" ) . arg ( & cargo_miri_path) ; // invoked with just a single path argument
935
+
936
+ if verbose {
937
+ eprintln ! ( "[cargo-miri rustdoc] {:?}" , cmd) ;
938
+ }
939
+
804
940
exec ( cmd)
805
941
}
806
942
@@ -817,6 +953,29 @@ fn main() {
817
953
return ;
818
954
}
819
955
956
+ // The way rustdoc invokes rustc is indistuingishable from the way cargo invokes rustdoc by the
957
+ // arguments alone. `phase_cargo_rustdoc` sets this environment variable to let us disambiguate.
958
+ let invoked_by_rustdoc = env:: var_os ( "MIRI_CALLED_FROM_RUSTDOC" ) . is_some ( ) ;
959
+ if invoked_by_rustdoc {
960
+ // ...however, we then also see this variable when rustdoc invokes us as the testrunner!
961
+ // The runner is invoked as `$runtool ($runtool-arg)* output_file`;
962
+ // since we don't specify any runtool-args, and rustdoc supplies multiple arguments to
963
+ // the test-builder unconditionally, we can just check the number of remaining arguments:
964
+ if args. len ( ) == 1 {
965
+ let arg = args. next ( ) . unwrap ( ) ;
966
+ let binary = Path :: new ( & arg) ;
967
+ if binary. exists ( ) {
968
+ phase_cargo_runner ( binary, args) ;
969
+ } else {
970
+ show_error ( format ! ( "`cargo-miri` called with non-existing path argument `{}` in rustdoc mode; please invoke this binary through `cargo miri`" , arg) ) ;
971
+ }
972
+ } else {
973
+ phase_cargo_rustc ( args) ;
974
+ }
975
+
976
+ return ;
977
+ }
978
+
820
979
// Dispatch to `cargo-miri` phase. There are three phases:
821
980
// - When we are called via `cargo miri`, we run as the frontend and invoke the underlying
822
981
// cargo. We set RUSTC_WRAPPER and CARGO_TARGET_RUNNER to ourselves.
@@ -829,16 +988,15 @@ fn main() {
829
988
Some ( "miri" ) => phase_cargo_miri ( args) ,
830
989
Some ( "rustc" ) => phase_cargo_rustc ( args) ,
831
990
Some ( arg) => {
832
- // We have to distinguish the "runner" and "rustfmt " cases.
991
+ // We have to distinguish the "runner" and "rustdoc " cases.
833
992
// As runner, the first argument is the binary (a file that should exist, with an absolute path);
834
- // as rustfmt , the first argument is a flag (`--something`).
993
+ // as rustdoc , the first argument is a flag (`--something`).
835
994
let binary = Path :: new ( arg) ;
836
995
if binary. exists ( ) {
837
996
assert ! ( !arg. starts_with( "--" ) ) ; // not a flag
838
997
phase_cargo_runner ( binary, args) ;
839
998
} else if arg. starts_with ( "--" ) {
840
- // We are rustdoc.
841
- eprintln ! ( "Running doctests is not currently supported by Miri." )
999
+ phase_cargo_rustdoc ( arg, args) ;
842
1000
} else {
843
1001
show_error ( format ! ( "`cargo-miri` called with unexpected first argument `{}`; please only invoke this binary through `cargo miri`" , arg) ) ;
844
1002
}
0 commit comments